diff --git a/media/css/seahub.css b/media/css/seahub.css index d238a2ba48..ec8ba519a2 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -128,6 +128,8 @@ .sf2-icon-x1:before { content:"\e01d"; } .sf2-icon-tick:before { content:"\e01e"; } .sf2-icon-x2:before { content:"\e01f"; } +.sf2-icon-user2:before { content:"\e020"; } +.sf2-icon-msgs2:before { content:"\e021"; } /******* tags **********/ html { background:#fff; } @@ -584,30 +586,6 @@ textarea:-moz-placeholder {/* for FF */ top:-11px; left:-10px; } -/**** info-item *****/ -.info-item { - border: 1px solid #eee; - border-radius: 2px; - margin-bottom: 12px; -} -.info-item h3, -.info-item p { - margin:0; -} -.info-item h3 { - font-size:1.15em; -} -.info-item-top { - padding: 4px 10px; - background: #f8f8f8; - border-bottom: 1px solid #eee; -} -.info-item-bottom { - padding: 8px 10px; -} -.info-item p.not-last { - margin-bottom:0.4em; -} /**** tabnav *****/ .tabnav-tabs, .tabnav-tab { @@ -690,6 +668,18 @@ textarea:-moz-placeholder {/* for FF */ color:#333; font-weight:normal; } +.side-tabnav-tabs .tab .grp-list a { + font-size:14px; + line-height:24px; + padding:0; +} +#group-nav .sharp { + display:inline-block; + width:42px; + margin-right:5px; + text-align:right; + color:#aaa; +} .side-tabnav-tabs .tab a:hover { background-color:#feefb8; text-decoration:none; @@ -704,12 +694,16 @@ textarea:-moz-placeholder {/* for FF */ line-height:1; color:#999; } +#group-nav .toggle-icon { + color:#999; +} .side-tabnav-tabs .tab-cur a, .side-tabnav-tabs .tab-cur a:hover { background-color:#feac74; } .side-tabnav-tabs .tab-cur [class^="sf2-icon-"], -.side-tabnav-tabs .tab-cur a { +.side-tabnav-tabs .tab-cur a, +#group-nav .tab-cur .sharp { color:#fff; } .side-tabnav-tabs .tab-cur a { @@ -1146,6 +1140,45 @@ textarea:-moz-placeholder {/* for FF */ color:#fff; text-decoration:none; } +/**** popover ****/ /* e.g. top notice popup, group members popup */ +.popover { + width:240px; + background:#fff; + border:1px solid #c9c9c9; + border-radius:3px; + box-shadow:0 0 1px #f3f3f3; +} +.popover-hd { + padding:5px 0 3px; + border-bottom:1px solid #dfdfe1; + margin:0 10px; +} +.popover-title { + text-align:center; + margin:0; +} +.popover-close { + font-size:16px; + color:#b9b9b9; + margin:4px 0 0; +} +.popover-con { + padding:0 10px; + overflow:auto; +} +/**** user-item ****/ /* e.g. group member in 'group members' panel */ +.user-item { + margin:5px 0; +} +.user-item .avatar { + border-radius:1000px; +} +.user-item .txt { + margin-left:40px; +} +.user-item .txt-item { + margin:0; +} /********** container ***********/ #main, #footer { width:950px; @@ -1209,6 +1242,11 @@ textarea:-moz-placeholder {/* for FF */ #organization-repos .hd { position:relative; } +#group-top .op-icon { + display:inline-block; + font-size:22px; + margin:4px 15px 0 0; +} .commit-list-topbar, .file-audit-list-topbar, .repo-file-list-topbar { @@ -1439,14 +1477,6 @@ textarea:-moz-placeholder {/* for FF */ #user-info-popup { width:220px; } -#top-nav-grp-info { - width:235px; - top:40px; - font-size:14px; -} -#top-nav-grp-info .outer-caret { - left:38px; -} #notice-popup .outer-caret { left:143px; } @@ -1463,35 +1493,10 @@ textarea:-moz-placeholder {/* for FF */ color:#333; font-weight:normal; } -#top-nav-grp-list .item { - padding:5px 10px; - border:none; -} -#top-nav-grp-list .item.hl { - background:#fafafa; - cursor:pointer; -} -#top-nav-grp-list .name { - display:inline-block; - width:142px; -} .top-info-popup a.item:hover { background:#fafafa; text-decoration:none; } -#top-nav-grp-info .avatar { - border-radius:1000px; - margin-right:5px; - vertical-align:middle; -} -#top-nav-grp-info .op-icon { - font-size:24px; - color:#999; - margin:0; -} -#top-nav-grp-info .all-grp { - padding-left:50px; -} #msg-file-share { margin-top:8px; position:relative; @@ -1529,34 +1534,6 @@ textarea:-moz-placeholder {/* for FF */ #logo { margin-right:30px; } -#header .nav { - float:left; - padding-top:8px; - font-size:15px; -} -#header .nav-item { - float: left; - height:21px; - padding-bottom:19px; - margin:0 14px; -} -.ru #header .nav-item { - margin:0 11px; -} -#header .nav .a, -#header .nav .a:visited { - color:#8A948F; - font-weight:bold; - text-decoration:none; - white-space:nowrap;/* for ipad */ -} -#header .nav .a.cur { - color:#000; -} -#header .nav .a:hover { - color: #ff9933; - text-decoration: underline; -} /* footer */ #footer { @@ -1835,6 +1812,7 @@ textarea:-moz-placeholder {/* for FF */ line-height:25px; margin:0; } +#group-top .path, #dir-view .path { font-size:16px; line-height:1.5; @@ -3757,6 +3735,7 @@ img.thumbnail { border-radius:3px; margin:0; } +/**** #groups ****/ /* groups page */ #groups .group-name { font-size:16px; margin:15px 0 10px; @@ -3767,3 +3746,15 @@ img.thumbnail { #groups .empty-tips { margin:10px 0 40px; } +/**** #group ****/ /* group page */ +#group { + position:relative; +} +#group-members { + position:absolute; + top:77px; + right:0; +} +#group-members .outer-caret { + right:66px; +} diff --git a/media/css/sf_font2/seafile-font2.eot b/media/css/sf_font2/seafile-font2.eot index 12bafe7f6c..d6de997a7a 100644 Binary files a/media/css/sf_font2/seafile-font2.eot and b/media/css/sf_font2/seafile-font2.eot differ diff --git a/media/css/sf_font2/seafile-font2.ttf b/media/css/sf_font2/seafile-font2.ttf index 61c9372204..3e834e1ab1 100644 Binary files a/media/css/sf_font2/seafile-font2.ttf and b/media/css/sf_font2/seafile-font2.ttf differ diff --git a/media/css/sf_font2/seafile-font2.woff b/media/css/sf_font2/seafile-font2.woff index c4e8ad7d0d..f805b5ae0d 100644 Binary files a/media/css/sf_font2/seafile-font2.woff and b/media/css/sf_font2/seafile-font2.woff differ diff --git a/seahub/api2/endpoints/groups.py b/seahub/api2/endpoints/groups.py index 4212ee3770..b9ef0015f7 100644 --- a/seahub/api2/endpoints/groups.py +++ b/seahub/api2/endpoints/groups.py @@ -20,9 +20,10 @@ from seahub.api2.throttling import UserRateThrottle from seahub.avatar.settings import GROUP_AVATAR_DEFAULT_SIZE from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, \ get_default_group_avatar_url -from seahub.utils import is_org_context +from seahub.utils import is_org_context, is_valid_username from seahub.utils.timeutils import dt, utc_to_local from seahub.group.utils import validate_group_name, check_group_name_conflict +from seahub.group.views import remove_group_common from seahub.base.templatetags.seahub_tags import email2nickname, \ translate_seahub_time @@ -167,3 +168,131 @@ class Groups(APIView): "admins": self._get_group_admins(g.id), } return Response(new_group, status=status.HTTP_201_CREATED) + + def put(self, request, group_id): + """ Rename, transfer a group + """ + + group_id = int(group_id) + try: + group = seaserv.get_group(group_id) + except SearpcError as e: + logger.error(e) + error_msg = _(u'Internal Server Error') + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + if not group: + error_msg = _(u'Group does not exist.') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # only group staff can rename or transfer a group + username = request.user.username + if not seaserv.check_group_staff(group_id, username): + error_msg = _(u'Permission denied') + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + operation = request.DATA.get('operation', '') + if operation.lower() == 'rename': + new_group_name = request.DATA.get('new_group_name', None) + if not new_group_name: + error_msg = _(u'Argument missing') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # Check whether group name is validate. + if not validate_group_name(new_group_name): + error_msg = _(u'Group name can only contain letters, numbers, blank, hyphen or underscore') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # Check whether group name is duplicated. + if check_group_name_conflict(request, new_group_name): + error_msg = _(u'There is already a group with that name.') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + try: + seaserv.ccnet_threaded_rpc.set_group_name(group_id, new_group_name) + except SearpcError as e: + logger.error(e) + error_msg = _(u'Internal Server Error') + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + elif operation.lower() == 'transfer': + email = request.DATA.get('email') + if not email: + error_msg = _(u'Argument missing') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if not is_valid_username(email): + error_msg = _('Email %s is not valid.') % email + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if email == group.creator_name: + error_msg = _('%s is already group owner') % email + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + try: + if not seaserv.is_group_user(group_id, email): + seaserv.ccnet_threaded_rpc.group_add_member(group_id, username, email) + + if not seaserv.check_group_staff(group_id, email): + seaserv.ccnet_threaded_rpc.group_set_admin(group_id, email) + + seaserv.ccnet_threaded_rpc.set_group_creator(group_id, email) + except SearpcError as e: + logger.error(e) + error_msg = _(u'Internal Server Error') + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + else: + error_msg = _(u'Operation can only be rename or transfer.') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + g = seaserv.get_group(group_id) + val = utc_to_local(dt(g.timestamp)) + avatar_url, is_default, date_uploaded = api_grp_avatar_url(group_id, + GROUP_AVATAR_DEFAULT_SIZE) + + group_info = { + "id": g.id, + "name": g.group_name, + "creator": g.creator_name, + "created_at": val.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(val).format('O'), + "avatar_url": request.build_absolute_uri(avatar_url), + "admins": self._get_group_admins(g.id), + } + return Response(group_info, status=status.HTTP_200_OK) + + def delete(self, request, group_id): + """ Delete a group + """ + + group_id = int(group_id) + try: + group = seaserv.get_group(group_id) + except SearpcError as e: + logger.error(e) + error_msg = _(u'Internal Server Error') + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + if not group: + error_msg = _(u'Group does not exist.') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # only group staff can delete a group + username = request.user.username + if not seaserv.check_group_staff(group_id, username): + error_msg = _(u'Permission denied') + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + org_id = None + if is_org_context(request): + org_id = request.user.org.org_id + + try: + remove_group_common(group_id, username, org_id=org_id) + except SearpcError as e: + logger.error(e) + error_msg = _(u'Internal Server Error') + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + return Response({'success': True}, status=status.HTTP_200_OK) + diff --git a/seahub/api2/views.py b/seahub/api2/views.py index 5972ba995e..ff72a3ad12 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -38,6 +38,8 @@ from .utils import is_repo_writable, is_repo_accessible, \ get_person_msgs, api_group_check, get_email, get_timestamp, \ get_group_message_json, get_group_msgs, get_group_msgs_json, get_diff_details, \ json_response, to_python_boolean, is_seafile_pro + +from seahub.avatar.settings import AVATAR_DEFAULT_SIZE from seahub.avatar.templatetags.avatar_tags import api_avatar_url, avatar from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, \ grp_avatar @@ -3468,10 +3470,64 @@ class Groups(APIView): "Operation can only be rename.") class GroupMembers(APIView): - authentication_classes = (TokenAuthentication, ) + authentication_classes = (TokenAuthentication, SessionAuthentication) permission_classes = (IsAuthenticated,) throttle_classes = (UserRateThrottle, ) + def get(self, request, group_id, format=None): + """ + Get group members. + """ + try: + group_id_int = int(group_id) + except ValueError: + return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid group ID') + + try: + avatar_size = int(request.GET.get('avatar_size', + AVATAR_DEFAULT_SIZE)) + except ValueError: + avatar_size = AVATAR_DEFAULT_SIZE + + try: + group = seaserv.get_group(group_id_int) + except SearpcError as e: + logger.error(e) + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, + 'Failed to get group.') + if not group: + return api_error(status.HTTP_404_NOT_FOUND, 'Group not found') + + try: + is_group_user = seaserv.is_group_user(group_id_int, + request.user.username) + except SearpcError as e: + logger.error(e) + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, + 'Failed to check if is group user.') + if not is_group_user: + return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.') + + try: + members = seaserv.get_group_members(group.id) + except SearpcError as e: + logger.error(e) + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, + 'Failed to get group members.') + members_json = [] + for m in members: + avatar_url, is_default, date_uploaded = api_avatar_url(m.user_name, + avatar_size) + + members_json.append({ + 'username': m.user_name, + "fullname": email2nickname(m.user_name), + "avatar_url": request.build_absolute_uri(avatar_url), + }) + + return HttpResponse(json.dumps(members_json), + content_type=json_content_type) + def put(self, request, group_id, format=None): """ Add group members. diff --git a/seahub/base/context_processors.py b/seahub/base/context_processors.py index 11480b42f0..78fb53ef68 100644 --- a/seahub/base/context_processors.py +++ b/seahub/base/context_processors.py @@ -50,9 +50,9 @@ def base(request): except AttributeError: base_template = 'myhome_base.html' - # get 8 user groups try: - grps = request.user.joined_groups[:8] + grps = request.user.joined_groups + grps.sort(lambda x, y: cmp(x.group_name.lower(), y.group_name.lower())) except AttributeError: # anonymous user grps = None diff --git a/seahub/group/templates/group/group_members.html b/seahub/group/templates/group/group_members.html deleted file mode 100644 index 4812526b37..0000000000 --- a/seahub/group/templates/group/group_members.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends 'group/group_base.html' %} - -{% load seahub_tags avatar_tags i18n %} -{% block cur_members %}tab-cur{%endblock%} - -{% block right_panel %} -
-

{% trans "Members" %}

-{% if group.view_perm != 'pub' and not is_staff %} - -{% endif %} -
- -{% endblock %} - -{% block extra_script %}{{block.super}} - - -{% endblock %} diff --git a/seahub/group/urls.py b/seahub/group/urls.py index 2104a908cd..38afffe871 100644 --- a/seahub/group/urls.py +++ b/seahub/group/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import patterns, url -from views import group_info, group_members, group_member_operations, group_add_admin, \ +from views import group_info, group_member_operations, group_add_admin, \ group_manage, msg_reply, msg_reply_new, group_recommend, \ create_group_repo, attention, group_message_remove, \ group_remove_admin, group_discuss, group_wiki, group_wiki_create, \ @@ -32,7 +32,6 @@ urlpatterns = patterns('', url(r'^(?P\d+)/revoke_pub/$', group_revoke_public, name='group_revoke_pub'), url(r'^(?P\d+)/quit/$', group_quit, name='group_quit'), url(r'^(?P[\d]+)/create-repo/$', create_group_repo, name='create_group_repo'), - url(r'^(?P[\d]+)/members/$', group_members, name='group_members'), (r'^(?P[\d]+)/member/(?P[^/]+)/$', group_member_operations), url(r'^(?P\d+)/msgdel/(?P\d+)/$', group_message_remove, name='group_message_remove'), url(r'^(?P\d+)/admin/add/$', group_add_admin, name='group_add_admin'), diff --git a/seahub/group/views.py b/seahub/group/views.py index c89a6af5a1..3778d68015 100644 --- a/seahub/group/views.py +++ b/seahub/group/views.py @@ -592,26 +592,6 @@ def group_info(request, group): 'repo_password_min_length': config.REPO_PASSWORD_MIN_LENGTH, }, context_instance=RequestContext(request)) -@group_check -def group_members(request, group): - if group.view_perm == 'pub': - raise Http404 - - # Get all group members. - members = get_group_members(group.id) - - # get available modules(wiki, etc) - mods_available = get_available_mods_by_group(group.id) - mods_enabled = get_enabled_mods_by_group(group.id) - - return render_to_response("group/group_members.html", { - "members": members, - "group" : group, - "is_staff": group.is_staff, - "mods_enabled": mods_enabled, - "mods_available": mods_available, - }, context_instance=RequestContext(request)) - def send_group_member_add_mail(request, group, from_user, to_user): c = { 'email': from_user, diff --git a/seahub/settings.py b/seahub/settings.py index 54291d4f06..53a41547cc 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -287,7 +287,7 @@ AVATAR_DEFAULT_URL = '/avatars/default.png' AVATAR_DEFAULT_NON_REGISTERED_URL = '/avatars/default-non-register.jpg' AVATAR_MAX_AVATARS_PER_USER = 1 AVATAR_CACHE_TIMEOUT = 14 * 24 * 60 * 60 -AUTO_GENERATE_AVATAR_SIZES = (16, 20, 24, 28, 32, 36, 40, 48, 60, 80, 290) +AUTO_GENERATE_AVATAR_SIZES = (16, 20, 24, 28, 32, 36, 40, 48, 60, 64, 80, 290) # Group avatar GROUP_AVATAR_STORAGE_DIR = 'avatars/groups' GROUP_AVATAR_DEFAULT_URL = 'avatars/groups/default.png' diff --git a/seahub/templates/base.html b/seahub/templates/base.html index bb474e7017..a95108aee4 100644 --- a/seahub/templates/base.html +++ b/seahub/templates/base.html @@ -180,42 +180,6 @@ $('#info-bar .close').click(function() { }); {% endif %} -{% if grps %} -$(function() { - var grp_nav = $('#top-nav-grp'); - grp_nav.after('
'); - var popup = $('#top-nav-grp-info'); - var popup_con = '
    '; - {% for grp in grps %} - popup_con += '
  • {% grp_avatar grp.id 36 %} {{ grp.group_name }}
  • '; - {% endfor %} - popup_con += '
'; - popup_con += '{% trans "All Groups" %}'; - popup.html(popup_con); - $('.item', popup).hover( - function() { - $(this).addClass('hl').children('a').removeClass('vh'); - }, - function() { - $(this).removeClass('hl').children('a').addClass('vh'); - } - ) - .click(function() { - location.href = $(this).data('url'); - }); - - popup.css({'right': (grp_nav.outerWidth() - popup.outerWidth())/6 * 5}); - grp_nav.parent('.nav-item').hover( - function() { - popup.removeClass('hide'); - }, - function() { - popup.addClass('hide'); - } - ); -}); -{% endif %} - {% if has_file_search %} {% include 'snippets/search_js.html' %} {% endif %} diff --git a/seahub/templates/base_for_backbone.html b/seahub/templates/base_for_backbone.html index bad051c583..808b8915d2 100644 --- a/seahub/templates/base_for_backbone.html +++ b/seahub/templates/base_for_backbone.html @@ -47,33 +47,7 @@ logo {% endif %} - {% block nav %} - - {% endblock %} + {% block nav %}{% endblock %} {% block header_right %} {% if request.user.is_authenticated %} diff --git a/seahub/templates/home_base.html b/seahub/templates/home_base.html index 7dec12da4e..0321384ce9 100644 --- a/seahub/templates/home_base.html +++ b/seahub/templates/home_base.html @@ -4,14 +4,43 @@ {% block left_panel %}
+

{% trans "Files" %}

+ +
-

{% trans "Personal" %}

+

{% trans "Tools" %}

{% if user.permissions.can_add_repo %} {% endif %}
    -
  • {% trans "Libraries" %}
  • {% trans "Starred" %}
  • {% if events_enabled %}
  • {% trans "Activities" %}
  • @@ -64,6 +93,11 @@ $('#enable-mods').click(function() { $('#simplemodal-container').css('height', 'auto'); return false; }); +$('#group-nav a:first').click(function() { + $('#group-nav .toggle-icon').toggleClass('icon-caret-left icon-caret-down'); + $('#group-nav .grp-list').slideToggle(); + return false; +}); {% endif %} {% endblock %} diff --git a/seahub/templates/js/templates.html b/seahub/templates/js/templates.html index 6c548e181e..14b9ccab59 100644 --- a/seahub/templates/js/templates.html +++ b/seahub/templates/js/templates.html @@ -90,23 +90,6 @@ <%= mtime_relative %> <%- owner_nickname %> - - + - + diff --git a/seahub/templates/libraries.html b/seahub/templates/libraries.html index b1158136c4..96e0fb9769 100644 --- a/seahub/templates/libraries.html +++ b/seahub/templates/libraries.html @@ -11,54 +11,16 @@ {% endblock %} {% block left_panel %} -
    -
    -
    -
    -
    -

    {% trans "Organization" %}

    - -
    -
    -

    {% trans "Tips" %}

    -
    - {% if user.permissions.can_add_group %} -

    {% trans "After creating a group, you can add members and share libraries into it." %}

    - {% else %} -

    {% trans "Since you are a guest user now, you can not create groups." %}

    - {% endif %} -
    -
    +
    {% endblock %} {% block right_panel %} - - -
    -
    - -
    - {% if user.permissions.can_add_repo %} - - {% endif %} - {% if sub_lib_enabled %} - - {% endif %} + {% if user.permissions.can_add_repo %} +
    +
    +

    {% trans "Mine" %}

    +
    -
    - -
    @@ -67,9 +29,17 @@

    {% trans "You have not created any libraries" %}

    {% trans "You can create a library to organize your files. For example, you can create one for each of your projects. Each library can be synchronized and shared separately." %}

    + +

    + {% endif %} -
    + {% if user.permissions.can_add_repo and sub_lib_enabled %} +
    +
    +

    {% trans "Sub-libraries" %}

    + +
    @@ -86,9 +56,13 @@

    {% trans "You have not created any sub-libraries" %}

    {% trans "You can create a sub-library from a directory inside a library. A sub-library can be independently synced and shared. Files in the sub-library will be automatically kept in sync with those in the directory of the origin library." %}

    + +

    + {% endif %} -
    +
    +

    {% trans "Shared" %}

    @@ -96,11 +70,10 @@

    {% trans "No library is shared to you" %}

    + +

    - -

    -

    {% trans "Starred" %}

    @@ -157,34 +130,41 @@ {% endif %} -
    -
    -

    {% trans "Libraries" %}

    - {% if user.permissions.can_add_repo %} - - {% endif %} -
    +
    +
    -
    -
    +
    +
    - + +

    - -

    +
    +
    +
    + +

    {% trans "Members" %}

    +
    +
    + +
      +

      +
      +
      -
      + +
      -

      {% trans "Libraries" %}

      +

      {% trans "Organization" %}

        @@ -255,17 +235,6 @@ app["pageOptions"] = { {% endfor %} return groups; })(), - top_nav_groups: (function () { - var groups = []; - {% for group in grps %} - groups.push({ - 'name': '{{group.group_name}}', - 'id': '{{group.id}}', - 'avatar': '{% grp_avatar group.id 36 %}' - }); - {% endfor %} - return groups; - })(), user_mods_available: (function () { var mods_available = []; {% for mod in request.user.mods_available %} diff --git a/seahub/templates/my_group_repos.html b/seahub/templates/my_group_repos.html deleted file mode 100644 index c2b0903ded..0000000000 --- a/seahub/templates/my_group_repos.html +++ /dev/null @@ -1,50 +0,0 @@ -{% load seahub_tags group_avatar_tags i18n %}
        - {% if group_repos %} - - - - - - - - - - - - {% for repo in group_repos %} - - {% if repo.show_group_name %} - - - {% else %} - - - {% endif %} - - - - {% if repo.last_modified %} - - {% else %} - - {% endif %} - - - {% endfor %} -
        {% trans "Group" %}{% trans "Name" %}{% trans "Description" %}{% trans "Last Update" %}{% trans "Shared By" %}
        {% grp_avatar repo.group.id 20 %}{{ repo.group.group_name }} - {% if repo.encrypted %} - {% trans - {% else %} - {% if repo.user_perm == 'rw' %} - {% trans - {% else %} - {% trans - {% endif %} - {% endif %} - {{ repo.repo_name }}{{ repo.repo_desc }}{{ repo.last_modified|translate_seahub_time }}--{{ repo.user|email2nickname }}
        - {% else %} -
        -

        {% trans "No library is shared to your groups" %}

        -
        - {% endif %} -
        diff --git a/seahub/templates/myhome_base.html b/seahub/templates/myhome_base.html index 4bdff041e8..6f541559b4 100644 --- a/seahub/templates/myhome_base.html +++ b/seahub/templates/myhome_base.html @@ -1,20 +1,4 @@ {% extends "base.html" %} {% load i18n %} {% block nav %} - {% endblock %} diff --git a/seahub/urls.py b/seahub/urls.py index b945bb7373..0555c5b0b7 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -98,6 +98,7 @@ urlpatterns = patterns( url(r'^lib/(?P[-0-9a-f]{36})/file(?P.*)$', view_lib_file, name='view_lib_file'), url(r'^#common/lib/(?P[-0-9a-f]{36})/(?P.*)$', fake_view, name='view_common_lib_dir'), url(r'^#group/(?P\d+)/$', fake_view, name='view_group'), + url(r'^#group/(?P\d+)/members/$', fake_view, name='group_members'), url(r'^#groups/', fake_view, name='group_list'), # url(r'^home/my/lib/(?P[-0-9a-f]{36})/dir/(?P.*)$', myhome_lib, name='myhome_lib'), @@ -192,6 +193,7 @@ urlpatterns = patterns( ### Apps ### (r'^api2/', include('seahub.api2.urls')), url(r'^api/v2.1/groups/$', Groups.as_view(), name='api-v2.1-groups'), + url(r'^api/v2.1/groups/(?P\d+)/$', Groups.as_view(), name='api-v2.1-group'), (r'^avatar/', include('seahub.avatar.urls')), (r'^notification/', include('seahub.notifications.urls')), (r'^contacts/', include('seahub.contacts.urls')), diff --git a/static/scripts/app/collections/group-members.js b/static/scripts/app/collections/group-members.js new file mode 100644 index 0000000000..4f64cc192f --- /dev/null +++ b/static/scripts/app/collections/group-members.js @@ -0,0 +1,19 @@ +define([ + 'underscore', + 'backbone', + 'common' +], function(_, Backbone, Common) { + 'use strict'; + + var GroupMemberCollection = Backbone.Collection.extend({ + setGroupId: function(group_id) { + this.group_id = group_id; + }, + + url: function() { + return Common.getUrl({name: 'group_members', group_id: this.group_id}); + } + }); + + return GroupMemberCollection; +}); diff --git a/static/scripts/app/router.js b/static/scripts/app/router.js index 92f435013a..173c7e9d5f 100644 --- a/static/scripts/app/router.js +++ b/static/scripts/app/router.js @@ -3,14 +3,14 @@ define([ 'jquery', 'backbone', 'common', + 'app/views/side-nav', 'app/views/myhome', 'app/views/groups', 'app/views/group', 'app/views/organization', - 'app/views/dir', - 'app/views/top-group-nav' -], function($, Backbone, Common, MyHomeView, GroupsView, GroupView, OrgView, - DirView, GroupNavView) { + 'app/views/dir' +], function($, Backbone, Common, SideNavView, MyHomeView, GroupsView, GroupView, + OrgView, DirView) { "use strict"; var Router = Backbone.Router.extend({ @@ -25,6 +25,7 @@ define([ 'groups/': 'showGroups', 'group/:group_id/': 'showGroupRepos', 'group/:group_id/lib/:repo_id(/*path)': 'showGroupRepoDir', + 'group/:group_id/members/': 'showGroupMembers', 'org/': 'showOrgRepos', 'org/lib/:repo_id(/*path)': 'showOrgRepoDir', 'common/lib/:repo_id(/*path)': 'showCommonDir', @@ -39,6 +40,8 @@ define([ Common.initAccountPopup(); Common.initNoticePopup(); + this.sideNavView = new SideNavView(); + this.dirView = new DirView(); this.myHomeView = new MyHomeView({dirView: this.dirView}); @@ -49,10 +52,6 @@ define([ this.currentView = this.myHomeView; - if (app.pageOptions.top_nav_groups.length > 0) { - this.topGroupNavView = new GroupNavView(); - } - $('#info-bar .close').click(Common.closeTopNoticeBar); $('#top-browser-tip .close').click(function () { $('#top-browser-tip').addClass('hide'); @@ -72,12 +71,14 @@ define([ this.myHomeView.showMyRepos(); } else { this.myHomeView.showSharedRepos(); + this.sideNavView.setCurTab('shared'); } }, showMyRepos: function() { this.switchCurrentView(this.myHomeView); this.myHomeView.showMyRepos(); + this.sideNavView.setCurTab('mine'); }, showMySubRepos: function() { @@ -88,16 +89,19 @@ define([ showSharedRepos: function() { this.switchCurrentView(this.myHomeView); this.myHomeView.showSharedRepos(); + this.sideNavView.setCurTab('shared'); }, showStarredFile: function() { this.switchCurrentView(this.myHomeView); this.myHomeView.showStarredFile(); + this.sideNavView.setCurTab('starred'); }, showActivities: function() { this.switchCurrentView(this.myHomeView); this.myHomeView.showActivities(); + this.sideNavView.setCurTab('activities'); }, showMyRepoDir: function(repo_id, path) { @@ -108,6 +112,7 @@ define([ } this.switchCurrentView(this.myHomeView); this.myHomeView.showDir('my-libs', repo_id, path); + this.sideNavView.setCurTab('mine'); }, showCommonDir: function(repo_id, path) { @@ -128,6 +133,7 @@ define([ } this.switchCurrentView(this.myHomeView); this.myHomeView.showDir('my-sub-libs', repo_id, path); + this.sideNavView.setCurTab('sub-libs'); }, showSharedRepoDir: function(repo_id, path) { @@ -138,16 +144,25 @@ define([ } this.switchCurrentView(this.myHomeView); this.myHomeView.showDir('shared-libs', repo_id, path); + this.sideNavView.setCurTab('shared'); }, showGroups: function () { this.switchCurrentView(this.groupsView); this.groupsView.show(); + this.sideNavView.setCurTab('group', { + 'cur_group_tab': 'groups', + 'cur_group_id': '' + }); }, showGroupRepos: function(group_id) { this.switchCurrentView(this.groupView); this.groupView.showRepoList(group_id); + this.sideNavView.setCurTab('group', { + 'cur_group_tab': '', + 'cur_group_id': group_id + }); }, showGroupRepoDir: function(group_id, repo_id, path) { @@ -158,11 +173,21 @@ define([ } this.switchCurrentView(this.groupView); this.groupView.showDir(group_id, repo_id, path); + this.sideNavView.setCurTab('group', { + 'cur_group_tab': '', + 'cur_group_id': group_id + }); + }, + + showGroupMembers: function(group_id) { + this.showGroupRepos(group_id); + this.groupView.showMembers(); }, showOrgRepos: function() { this.switchCurrentView(this.orgView); this.orgView.showRepoList(); + this.sideNavView.setCurTab('org'); }, showOrgRepoDir: function(repo_id, path) { @@ -173,6 +198,7 @@ define([ } this.switchCurrentView(this.orgView); this.orgView.showDir(repo_id, path); + this.sideNavView.setCurTab('org'); } }); diff --git a/static/scripts/app/views/group-member.js b/static/scripts/app/views/group-member.js new file mode 100644 index 0000000000..6d6eeeb043 --- /dev/null +++ b/static/scripts/app/views/group-member.js @@ -0,0 +1,47 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common' +], function($, _, Backbone, Common) { + 'use strict'; + + var View = Backbone.View.extend({ + tagName: 'li', + className: 'user-item cspt ovhd', + + template: _.template($('#group-member-tmpl').html()), + + events: { + 'mouseenter': 'highlight', + 'mouseleave': 'rmHighlight', + 'click': 'visitMemberProfile' + }, + + initialize: function() { + }, + + render: function() { + this.$el.html(this.template(this.model.attributes)); + return this; + }, + + highlight: function() { + this.$el.addClass('hl'); + }, + + rmHighlight: function() { + this.$el.removeClass('hl'); + }, + + visitMemberProfile: function() { + location.href = Common.getUrl({ + 'name': 'user_profile', + 'username': encodeURIComponent(this.model.get('email')) + }); + } + + }); + + return View; +}); diff --git a/static/scripts/app/views/group-members.js b/static/scripts/app/views/group-members.js new file mode 100644 index 0000000000..29012b8d86 --- /dev/null +++ b/static/scripts/app/views/group-members.js @@ -0,0 +1,110 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', + 'app/collections/group-members', + 'app/views/group-member' +], function($, _, Backbone, Common, GroupMembers, ItemView) { + 'use strict'; + + var View = Backbone.View.extend({ + el: '#group-members', + + initialize: function(options) { + this.collection = new GroupMembers(); + this.listenTo(this.collection, 'add', this.addOne); + this.listenTo(this.collection, 'reset', this.reset); + + this.$loadingTip = this.$('.loading-tip'); + this.$listContainer = $('#group-member-list'); + this.$error = this.$('.error'); + + var _this = this; + $(window).resize(function() { + _this.setConMaxHeight(); + }); + $(document).click(function(e) { + var target = e.target || event.srcElement; + var $popup = _this.$el, + $popup_switch = $('#group-members-icon'); + + if ($('#group-members:visible').length && + !$popup.is(target) && + !$popup.find('*').is(target) && + !$popup_switch.is(target)) { + _this.hide(); + } + }); + }, + + events: { + 'click .close': 'hide' + }, + + addOne: function(item, collection, options) { + var view = new ItemView({ + model: item + }); + this.$listContainer.append(view.render().el); + }, + + reset: function() { + this.$error.hide(); + this.$loadingTip.hide(); + this.$listContainer.empty(); + this.collection.each(this.addOne, this); + this.$listContainer.show(); + }, + + render: function() { + this.$listContainer.hide(); + this.$loadingTip.show(); + + var _this = this; + this.collection.setGroupId(this.group_id); + this.collection.fetch({ + cache: false, + reset: true, + data: {'avatar_size': 64}, + success: function(collection, response, opts) { + }, + error: function(collection, response, opts) { + _this.$loadingTip.hide(); + var err_msg; + if (response.responseText) { + if (response['status'] == 401 || response['status'] == 403) { + err_msg = gettext("Permission error"); + } else { + err_msg = gettext("Error"); + } + } else { + err_msg = gettext('Please check the network.'); + } + _this.$error.html(err_msg).show(); + } + }); + }, + + // set max-height for '.popover-con' + setConMaxHeight: function() { + this.$('.popover-con').css({'max-height': $(window).height() - this.$el.offset().top - this.$('.popover-hd').outerHeight(true) - 2}); // 2: top, bottom border width of $el + }, + + show: function(options) { + this.group_id = options.group_id; + this.$el.show(); + this.setConMaxHeight(); + this.render(); + app.router.navigate('group/' + this.group_id + '/members/'); + }, + + hide: function() { + this.$el.hide(); + app.router.navigate('group/' + this.group_id + '/'); + } + + }); + + return View; +}); diff --git a/static/scripts/app/views/group-side-nav.js b/static/scripts/app/views/group-side-nav.js deleted file mode 100644 index 5a7e82773d..0000000000 --- a/static/scripts/app/views/group-side-nav.js +++ /dev/null @@ -1,96 +0,0 @@ -define([ - 'jquery', - 'underscore', - 'backbone', - 'common' -], function($, _, Backbone, Common) { - 'use strict'; - - var GroupSideNavView = Backbone.View.extend({ - el: '#group-side-nav', - - template: _.template($("#group-side-nav-tmpl").html()), - enableModTemplate: _.template($("#group-mods-enable-form-tmpl").html()), - - initialize: function() { - }, - - render: function (group_id) { - this.group_id = group_id; - var _this = this; - $.ajax({ - url: Common.getUrl({ - 'name': 'group_basic_info', - 'group_id': this.group_id - }), - cache: false, - dataType: 'json', - success: function (data) { - _this.$el.html(_this.template(data)); - // for 'enable mod' - _this.mods_available = data.mods_available; - _this.mods_enabled = data.mods_enabled; - }, - error: function(xhr) { - var err_msg; - if (xhr.responseText) { - err_msg = $.parseJSON(xhr.responseText).error; - } else { - err_msg = gettext("Please check the network."); - } - _this.$el.html('

        ' + err_msg + '

        '); - } - }); - }, - - events: { - 'click #enable-mods': 'enableMods' - }, - - enableMods: function () { - var form = $(this.enableModTemplate({ - 'mods_available': this.mods_available, - 'mods_enabled': this.mods_enabled - })); - form.modal(); - $('#simplemodal-container').css('height', 'auto'); - $('.checkbox-orig', form).click(function() { - $(this).parent().toggleClass('checkbox-checked'); - }); - var checkbox = $('[name="group_wiki"]'); - var original_checked = checkbox.prop('checked'); - var _this = this; - form.submit(function() { - var cur_checked = checkbox.prop('checked'); - if (cur_checked == original_checked) { - return false; - } - Common.ajaxPost({ - form: form, - form_id: form.attr('id'), - post_url: Common.getUrl({ - 'name': 'toggle_group_modules', - 'group_id': _this.group_id - }), - post_data: {'group_wiki': cur_checked }, - after_op_success: function () { - $.modal.close(); - _this.render(_this.group_id); - } - }); - return false; - }); - }, - - show: function() { - this.$el.show(); - }, - - hide: function() { - this.$el.hide(); - } - - }); - - return GroupSideNavView; -}); diff --git a/static/scripts/app/views/group.js b/static/scripts/app/views/group.js index cd51ffa4e3..018c4f4fce 100644 --- a/static/scripts/app/views/group.js +++ b/static/scripts/app/views/group.js @@ -6,17 +6,19 @@ define([ 'app/collections/group-repos', 'app/views/group-repo', 'app/views/add-group-repo', - 'app/views/group-side-nav' + 'app/views/group-members' ], function($, _, Backbone, Common, GroupRepos, GroupRepoView, - AddGroupRepoView, GroupSideNavView) { + AddGroupRepoView, GroupMembersView) { 'use strict'; var GroupView = Backbone.View.extend({ - el: '#group-repo-tabs', + el: '#group', + groupTopTemplate: _.template($('#group-top-tmpl').html()), reposHdTemplate: _.template($('#shared-repos-hd-tmpl').html()), events: { + 'click #group-members-icon': 'toggleMembersPanel', 'click .repo-create': 'createRepo', 'click .by-name': 'sortByName', 'click .by-time': 'sortByTime' @@ -27,16 +29,16 @@ define([ this.$table = this.$('table'); this.$tableHead = this.$('thead'); this.$tableBody = this.$('tbody'); - this.$loadingTip = this.$('.loading-tip'); - this.$emptyTip = this.$('.empty-tips'); - - this.sideNavView = new GroupSideNavView(); + this.$loadingTip = this.$('#group-repos .loading-tip'); + this.$emptyTip = this.$('#group-repos .empty-tips'); this.repos = new GroupRepos(); this.listenTo(this.repos, 'add', this.addOne); this.listenTo(this.repos, 'reset', this.reset); this.dirView = options.dirView; + + this.membersView = new GroupMembersView(); }, addOne: function(repo, collection, options) { @@ -71,21 +73,36 @@ define([ } }, - showSideNav: function () { - var sideNavView = this.sideNavView; - if (sideNavView.group_id && sideNavView.group_id == this.group_id) { - sideNavView.show(); - return; - } - sideNavView.render(this.group_id); - sideNavView.show(); + renderGroupTop: function(group_id) { + var _this = this; + var $groupTop = $('#group-top'); + $.ajax({ + url: Common.getUrl({ + 'name': 'group_basic_info', + 'group_id': group_id + }), + cache: false, + dataType: 'json', + success: function (data) { + $groupTop.html(_this.groupTopTemplate(data)); + }, + error: function(xhr) { + var err_msg; + if (xhr.responseText) { + err_msg = $.parseJSON(xhr.responseText).error; + } else { + err_msg = gettext("Please check the network."); + } + $groupTop.html('

        ' + err_msg + '

        '); + } + }); }, showRepoList: function(group_id) { this.group_id = group_id; - this.showSideNav(); this.dirView.hide(); this.$emptyTip.hide(); + this.renderGroupTop(group_id); this.$tabs.show(); this.$table.hide(); var $loadingTip = this.$loadingTip; @@ -122,7 +139,6 @@ define([ showDir: function(group_id, repo_id, path) { this.group_id = group_id; - this.showSideNav(); this.hideRepoList(); this.dirView.showDir('group/' + this.group_id, repo_id, path); }, @@ -169,10 +185,22 @@ define([ }, hide: function() { - this.sideNavView.hide(); this.hideRepoList(); this.dirView.hide(); this.$emptyTip.hide(); + }, + + showMembers: function() { + this.membersView.show({'group_id': this.group_id}); + }, + + toggleMembersPanel: function() { + var panel_id = this.membersView.el.id; + if ($('#' + panel_id + ':visible').length) { // the panel is shown + this.membersView.hide(); + } else { + this.showMembers(); + } } }); diff --git a/static/scripts/app/views/groups.js b/static/scripts/app/views/groups.js index eed9d3314c..655699a0df 100644 --- a/static/scripts/app/views/groups.js +++ b/static/scripts/app/views/groups.js @@ -16,7 +16,6 @@ define([ this.listenTo(this.groups, 'add', this.addOne); this.listenTo(this.groups, 'reset', this.reset); - this.$sideTips = $('#groups-side-tips'); this.$loadingTip = this.$('.loading-tip'); this.$groupList = $('#group-list'); this.$emptyTip = this.$('.empty-tips'); @@ -82,13 +81,11 @@ define([ }, show: function() { - this.$sideTips.show(); this.$el.show(); this.render(); }, hide: function() { - this.$sideTips.hide(); this.$el.hide(); }, diff --git a/static/scripts/app/views/myhome-repos.js b/static/scripts/app/views/myhome-repos.js index 2396dd484a..79ac306216 100644 --- a/static/scripts/app/views/myhome-repos.js +++ b/static/scripts/app/views/myhome-repos.js @@ -10,23 +10,22 @@ define([ 'use strict'; var ReposView = Backbone.View.extend({ - el: $('#repo-tabs'), + el: $('#my-own-repos'), reposHdTemplate: _.template($('#my-repos-hd-tmpl').html()), events: { 'click .repo-create': 'createRepo', - 'click #my-own-repos .by-name': 'sortByName', - 'click #my-own-repos .by-time': 'sortByTime' + 'click .by-name': 'sortByName', + 'click .by-time': 'sortByTime' }, initialize: function(options) { - this.$tabs = $('#repo-tabs'); - this.$table = this.$('#my-own-repos table'); + this.$table = this.$('table'); this.$tableHead = $('thead', this.$table); this.$tableBody = $('tbody', this.$table); - this.$loadingTip = $('.loading-tip', this.$tabs); - this.$emptyTip = $('#my-own-repos .empty-tips'); + this.$loadingTip = this.$('.loading-tip'); + this.$emptyTip = this.$('.empty-tips'); this.$repoCreateBtn = this.$('.repo-create'); this.repos = new RepoCollection(); @@ -68,8 +67,7 @@ define([ }, showMyRepos: function() { - this.$tabs.show(); - $('#mylib-tab').parent().addClass('ui-state-active'); + this.$el.show(); this.$table.hide(); var $loadingTip = this.$loadingTip; $loadingTip.show(); @@ -98,16 +96,11 @@ define([ }, show: function() { - this.$repoCreateBtn.show(); this.showMyRepos(); }, hide: function() { - this.$repoCreateBtn.hide(); this.$el.hide(); - this.$table.hide(); - this.$emptyTip.hide(); - $('#mylib-tab', this.$tabs).parent().removeClass('ui-state-active'); }, createRepo: function() { diff --git a/static/scripts/app/views/myhome-shared-repos.js b/static/scripts/app/views/myhome-shared-repos.js index 872bc511fe..f5e94e261a 100644 --- a/static/scripts/app/views/myhome-shared-repos.js +++ b/static/scripts/app/views/myhome-shared-repos.js @@ -9,17 +9,16 @@ define([ 'use strict'; var SharedReposView = Backbone.View.extend({ - el: $('#repo-tabs'), + el: $('#repos-shared-to-me'), reposHdTemplate: _.template($('#shared-repos-hd-tmpl').html()), initialize: function(options) { - this.$tabs = $('#repo-tabs'); - this.$table = $('#repos-shared-to-me table'); + this.$table = this.$('table'); this.$tableHead = $('thead', this.$table); this.$tableBody = $('tbody', this.$table); - this.$loadingTip = $('.loading-tip', this.$tabs); - this.$emptyTip = $('#repos-shared-to-me .empty-tips'); + this.$loadingTip = this.$('.loading-tip'); + this.$emptyTip = this.$('.empty-tips'); this.repos = new RepoCollection({type: 'shared'}); this.listenTo(this.repos, 'add', this.addOne); @@ -55,8 +54,7 @@ define([ }, showSharedRepos: function() { - this.$tabs.show(); - $('#shared-lib-tab').parent().addClass('ui-state-active'); + this.$el.show(); this.$table.hide(); var $loadingTip = this.$loadingTip; $loadingTip.show(); @@ -89,14 +87,11 @@ define([ hide: function() { this.$el.hide(); - this.$table.hide(); - this.$emptyTip.hide(); - $('#shared-lib-tab', this.$tabs).parent().removeClass('ui-state-active'); }, events: { - 'click #repos-shared-to-me .by-name': 'sortByName', - 'click #repos-shared-to-me .by-time': 'sortByTime' + 'click .by-name': 'sortByName', + 'click .by-time': 'sortByTime' }, sortByName: function() { diff --git a/static/scripts/app/views/myhome-sub-repos.js b/static/scripts/app/views/myhome-sub-repos.js index d63df43457..bdc36ae9d9 100644 --- a/static/scripts/app/views/myhome-sub-repos.js +++ b/static/scripts/app/views/myhome-sub-repos.js @@ -11,19 +11,18 @@ define([ 'use strict'; var ReposView = Backbone.View.extend({ - el: $('#repo-tabs'), + el: $('#my-sub-repos'), events: { 'click #sub-lib-create': 'createRepo' }, initialize: function(options) { - this.$tabs = $('#repo-tabs'); - this.$table = this.$('#my-sub-repos table'); + this.$table = this.$('table'); this.$tableHead = $('thead', this.$table); this.$tableBody = $('tbody', this.$table); - this.$loadingTip = $('.loading-tip', this.$tabs); - this.$emptyTip = $('#my-sub-repos .empty-tips'); + this.$loadingTip = this.$('.loading-tip'); + this.$emptyTip = this.$('.empty-tips'); this.repos = new RepoCollection({type: 'sub'}); this.listenTo(this.repos, 'add', this.addOne); @@ -54,8 +53,7 @@ define([ }, showSubRepos: function() { - this.$tabs.show(); - $('#sublib-tab').parent().addClass('ui-state-active'); + this.$el.show(); this.$table.hide(); var $loadingTip = this.$loadingTip; $loadingTip.show(); @@ -83,16 +81,11 @@ define([ }, show: function() { - $('#sub-lib-create').show(); this.showSubRepos(); }, hide: function() { - $('#sub-lib-create').hide(); this.$el.hide(); - this.$table.hide(); - this.$emptyTip.hide(); - $('#sublib-tab', this.$tabs).parent().removeClass('ui-state-active'); }, createRepo: function() { diff --git a/static/scripts/app/views/myhome.js b/static/scripts/app/views/myhome.js index 1262bb0597..d7fc6f0223 100644 --- a/static/scripts/app/views/myhome.js +++ b/static/scripts/app/views/myhome.js @@ -7,17 +7,15 @@ define([ 'app/views/myhome-sub-repos', 'app/views/myhome-shared-repos', 'app/views/starred-file', - 'app/views/activities', - 'app/views/myhome-side-nav' + 'app/views/activities' ], function($, _, Backbone, Common, ReposView, SubReposView, - SharedReposView, StarredFileView, ActivitiesView, MyhomeSideNavView) { + SharedReposView, StarredFileView, ActivitiesView) { 'use strict'; var MyHomeView = Backbone.View.extend({ el: '#main', initialize: function(options) { - this.sideNavView = new MyhomeSideNavView(); this.reposView = new ReposView(); this.subReposView = new SubReposView(); this.sharedReposView = new SharedReposView(); @@ -32,42 +30,36 @@ define([ }, showMyRepos: function() { - this.sideNavView.show(); this.currentView.hide(); this.reposView.show(); this.currentView = this.reposView; }, showMySubRepos: function() { - this.sideNavView.show(); this.currentView.hide(); this.subReposView.show(); this.currentView = this.subReposView; }, showSharedRepos: function() { - this.sideNavView.show(); this.currentView.hide(); this.sharedReposView.show(); this.currentView = this.sharedReposView; }, showStarredFile: function() { - this.sideNavView.show({'cur_tab': 'starred'}); this.currentView.hide(); this.starredFileView.show(); this.currentView = this.starredFileView; }, showActivities: function() { - this.sideNavView.show({'cur_tab': 'activities'}); this.currentView.hide(); this.activitiesView.show(); this.currentView = this.activitiesView; }, showDir: function(category, repo_id, path) { - this.sideNavView.show(); var path = path || '/'; this.currentView.hide(); this.dirView.showDir(category, repo_id, path); @@ -76,7 +68,6 @@ define([ hide: function() { this.currentView.hide(); - this.sideNavView.hide(); } }); diff --git a/static/scripts/app/views/organization.js b/static/scripts/app/views/organization.js index f5073c4a77..ed81fa0d51 100644 --- a/static/scripts/app/views/organization.js +++ b/static/scripts/app/views/organization.js @@ -12,19 +12,16 @@ define([ 'use strict'; var OrganizationView = Backbone.View.extend({ - el: '#main', + el: '#organization-repos', reposHdTemplate: _.template($('#shared-repos-hd-tmpl').html()), initialize: function(options) { - - this.$sideNav = $('#org-side-nav'); - this.$reposDiv = $('#organization-repos'); - this.$table = $('#organization-repos table'); + this.$table = this.$('table'); this.$tableHead = $('thead', this.$table); this.$tableBody = $('tbody', this.$table); - this.$loadingTip = $('#organization-repos .loading-tip'); - this.$emptyTip = $('#organization-repos .empty-tips'); + this.$loadingTip = this.$('.loading-tip'); + this.$emptyTip = this.$('.empty-tips'); this.repos = new PubRepoCollection(); this.listenTo(this.repos, 'add', this.addOne); @@ -56,10 +53,10 @@ define([ }, events: { - 'click #organization-repos .share-existing': 'addRepo', - 'click #organization-repos .create-new': 'createRepo', - 'click #organization-repos .by-name': 'sortByName', - 'click #organization-repos .by-time': 'sortByTime' + 'click .share-existing': 'addRepo', + 'click .create-new': 'createRepo', + 'click .by-name': 'sortByName', + 'click .by-time': 'sortByTime' }, createRepo: function() { @@ -99,9 +96,8 @@ define([ }, showRepoList: function() { - this.$sideNav.show(); this.dirView.hide(); - this.$reposDiv.show(); + this.$el.show(); var $loadingTip = this.$loadingTip; $loadingTip.show(); var _this = this; @@ -129,11 +125,10 @@ define([ }, hideRepoList: function() { - this.$reposDiv.hide(); + this.$el.hide(); }, showDir: function(repo_id, path) { - this.$sideNav.show(); var path = path || '/'; this.hideRepoList(); this.dirView.showDir('org', repo_id, path); @@ -177,7 +172,6 @@ define([ }, hide: function() { - this.$sideNav.hide(); this.hideRepoList(); this.$emptyTip.hide(); this.dirView.hide(); diff --git a/static/scripts/app/views/myhome-side-nav.js b/static/scripts/app/views/side-nav.js similarity index 74% rename from static/scripts/app/views/myhome-side-nav.js rename to static/scripts/app/views/side-nav.js index b99b0f7682..bd51f8b945 100644 --- a/static/scripts/app/views/myhome-side-nav.js +++ b/static/scripts/app/views/side-nav.js @@ -6,21 +6,22 @@ define([ ], function($, _, Backbone, Common) { 'use strict'; - var MyhomeSideNavView = Backbone.View.extend({ - el: '#myhome-side-nav', + var sideNavView = Backbone.View.extend({ + el: '#side-nav', - template: _.template($("#myhome-side-nav-tmpl").html()), + template: _.template($("#side-nav-tmpl").html()), enableModTemplate: _.template($("#myhome-mods-enable-form-tmpl").html()), initialize: function() { - this.default_cur_tab = 'libs'; + this.default_cur_tab = 'mine'; this.data = { 'cur_tab': this.default_cur_tab, + 'show_group_list': false, // when cur_tab is not 'group' 'mods_enabled': app.pageOptions.user_mods_enabled, 'can_add_repo': app.pageOptions.can_add_repo, - 'events_enabled': app.pageOptions.events_enabled }; this.render(); + this.$el.show(); }, render: function() { @@ -29,7 +30,14 @@ define([ }, events: { - 'click #myhome-enable-mods': 'enableMods' + 'click #group-nav a:first': 'toggleGroupList', + 'click #enable-mods': 'enableMods' + }, + + toggleGroupList: function () { + $('#group-nav .toggle-icon').toggleClass('icon-caret-left icon-caret-down'); + $('#group-nav .grp-list').slideToggle(); + return false; }, enableMods: function () { @@ -77,24 +85,16 @@ define([ }); }, - show: function(options) { - if (options && options.cur_tab) { - this.data.cur_tab = options.cur_tab; - this.render(); - } else { - if (this.data.cur_tab != this.default_cur_tab) { - this.data.cur_tab = this.default_cur_tab; - this.render(); - } + setCurTab: function (cur_tab, options) { + this.data.cur_tab = cur_tab || this.default_cur_tab; + if (options) { + $.extend(this.data, options); } - this.$el.show(); - }, - - hide: function() { - this.$el.hide(); + this.data.show_group_list = $('#group-nav .grp-list:visible').length ? true : false; + this.render(); } }); - return MyhomeSideNavView; + return sideNavView; }); diff --git a/static/scripts/app/views/top-group-nav.js b/static/scripts/app/views/top-group-nav.js deleted file mode 100644 index 889c3e509a..0000000000 --- a/static/scripts/app/views/top-group-nav.js +++ /dev/null @@ -1,52 +0,0 @@ -define([ - 'jquery', - 'underscore', - 'backbone', - 'common' -], function($, _, Backbone, Common) { - 'use strict'; - - var GroupNavView = Backbone.View.extend({ - el: '.nav .nav-item-group', - - popupTemplate: _.template($('#top-group-nav-tmpl').html()), - - initialize: function() { - var popup = $(this.popupTemplate({groups: app.pageOptions.top_nav_groups})); - this.$el.append(popup); - popup.css({'right': ($('#top-nav-grp').outerWidth() - popup.outerWidth())/6 * 5}); - this.popup = popup; - }, - - events: { - 'mouseenter': 'showPopup', - 'mouseleave': 'hidePopup', - 'mouseenter #top-nav-grp-list .item': 'highlightGroupItem', - 'mouseleave #top-nav-grp-list .item': 'rmHighlightGroupItem', - 'click #top-nav-grp-list .item': 'visitGroup' - }, - - showPopup: function(e) { - this.popup.removeClass('hide'); - }, - - hidePopup: function(e) { - this.popup.addClass('hide'); - }, - - highlightGroupItem: function(e) { - $(e.currentTarget).addClass('hl').children('a').removeClass('vh'); - }, - - rmHighlightGroupItem: function(e) { - $(e.currentTarget).removeClass('hl').children('a').addClass('vh'); - }, - - visitGroup: function(e) { - this.hidePopup(e); - location.href = $(e.currentTarget).attr('data-url'); - } - }); - - return GroupNavView; -}); diff --git a/static/scripts/common.js b/static/scripts/common.js index c6db8fc5ad..e8bc324224 100644 --- a/static/scripts/common.js +++ b/static/scripts/common.js @@ -104,6 +104,7 @@ define([ case 'repo_set_password': return siteRoot + 'repo/set_password/'; case 'groups': return siteRoot + 'api/v2.1/groups/'; case 'group_repos': return siteRoot + 'api2/groups/' + options.group_id + '/repos/'; + case 'group_members': return siteRoot + 'api/v2.1/groups/' + options.group_id + '/members/'; case 'group_basic_info': return siteRoot + 'ajax/group/' + options.group_id + '/basic-info/'; case 'toggle_group_modules': return siteRoot + 'ajax/group/' + options.group_id + '/toggle-modules/'; case 'toggle_personal_modules': return siteRoot + 'ajax/toggle-personal-modules/'; @@ -117,6 +118,7 @@ define([ case 'search_user': return siteRoot + 'api2/search-user/'; case 'dir_shared_items': return siteRoot + 'api2/repos/' + options.repo_id + '/dir/shared_items/'; case 'events': return siteRoot + 'api2/events/'; + case 'user_profile': return siteRoot + 'profile/' + options.username + '/'; } }, diff --git a/tests/api/endpoints/test_groups.py b/tests/api/endpoints/test_groups.py index 9f2875fb69..09946c4e39 100644 --- a/tests/api/endpoints/test_groups.py +++ b/tests/api/endpoints/test_groups.py @@ -7,6 +7,7 @@ from seaserv import seafile_api from seahub.test_utils import BaseTestCase from seahub.api2.endpoints.groups import Groups +from tests.common.utils import randstring class GroupsTest(BaseTestCase): @@ -61,7 +62,7 @@ class GroupsTest(BaseTestCase): assert self.group_id in group_ids def test_create_group(self): - new_group_name = 'new-group-1' + new_group_name = 'new-group-' + randstring(6) resp = self.client.post(self.url, {'group_name': new_group_name}) self.assertEqual(201, resp.status_code) @@ -74,7 +75,7 @@ class GroupsTest(BaseTestCase): self.remove_group(json_resp['id']) def test_create_group_with_cn_name(self): - new_group_name = u'中文' + new_group_name = u'中文' + randstring(6) resp = self.client.post(self.url, {'group_name': new_group_name}) self.assertEqual(201, resp.status_code) @@ -90,15 +91,53 @@ class GroupsTest(BaseTestCase): self.assertEqual(400, resp.status_code) def test_can_not_create_group_with_invalid_name(self): - group_name = 'new%group-2' + new_group_name = 'new%group-' + randstring(6) - resp = self.client.post(self.url, {'group_name': group_name}) + resp = self.client.post(self.url, {'group_name': new_group_name}) self.assertEqual(400, resp.status_code) @patch.object(Groups, '_can_add_group') def test_can_not_create_group_with_invalid_permission(self, mock_can_add_group): mock_can_add_group.return_value = False - group_name = 'new-group-3' + new_group_name = 'new-group-' + randstring(6) - resp = self.client.post(self.url, {'group_name': group_name}) + resp = self.client.post(self.url, {'group_name': new_group_name}) self.assertEqual(403, resp.status_code) + + def test_can_rename_group(self): + new_group_name = 'new-group-' + randstring(6) + url = reverse('api-v2.1-group', args=[self.group_id]) + data = 'operation=rename&new_group_name=%s' % new_group_name + + resp = self.client.put(url, data, 'application/x-www-form-urlencoded') + self.assertEqual(200, resp.status_code) + + json_resp = json.loads(resp.content) + assert json_resp['name'] == new_group_name + + def test_can_transfer_group(self): + new_creator = self.admin.email + url = reverse('api-v2.1-group', args=[self.group_id]) + data = 'operation=transfer&email=%s' % new_creator + + resp = self.client.put(url, data, 'application/x-www-form-urlencoded') + self.assertEqual(200, resp.status_code) + + json_resp = json.loads(resp.content) + assert json_resp['creator'] == new_creator + + def test_can_not_transfer_group_to_group_owner(self): + new_creator = self.user.email + url = reverse('api-v2.1-group', args=[self.group_id]) + data = 'operation=transfer&email=%s' % new_creator + + resp = self.client.put(url, data, 'application/x-www-form-urlencoded') + self.assertEqual(400, resp.status_code) + + def test_can_delete_group(self): + url = reverse('api-v2.1-group', args=[self.group_id]) + resp = self.client.delete(url) + self.assertEqual(200, resp.status_code) + + json_resp = json.loads(resp.content) + assert json_resp['success'] is True diff --git a/tests/seahub/views/test_sysadmin.py b/tests/seahub/views/test_sysadmin.py index cbfd7452c7..891a9cb939 100644 --- a/tests/seahub/views/test_sysadmin.py +++ b/tests/seahub/views/test_sysadmin.py @@ -198,6 +198,8 @@ class UserInfoTest(BaseTestCase): self.admin_repo_id = r.id # set common user as staff in admin user's group + ccnet_threaded_rpc.group_add_member(self.admin_group_1_id, + self.admin.email, self.user.email) ccnet_threaded_rpc.group_set_admin(self.admin_group_1_id, self.user.email) # add common user to admin user's another group