diff --git a/media/css/seahub.css b/media/css/seahub.css index 6ef571b6d8..43d2f76a6a 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -1175,7 +1175,7 @@ textarea:-moz-placeholder {/* for FF */ background:#fff; border:1px solid #c9c9c9; border-radius:3px; - box-shadow:0 0 1px #f3f3f3; + box-shadow:0 0 4px #ccc; position:absolute; } .popover-hd { @@ -1207,12 +1207,20 @@ textarea:-moz-placeholder {/* for FF */ margin:0; } /**** dropdown menu ******/ +.dropdown { + position:relative; +} +.dropdown-inline { + display:inline-block; +} .dropdown-menu { position:absolute; + left:0px; background:#fff; padding:6px 1px; - border:1px solid #eee; - border-radius:5px; + border:1px solid rgba(34,36,38,.15); + border-radius:3px; + box-shadow:0 2px 3px 0 rgba(34,36,38,.15); z-index:10; } .dropdown-menu li a, @@ -1222,6 +1230,7 @@ textarea:-moz-placeholder {/* for FF */ min-width:110px; white-space:nowrap; color:#444; + font-weight:normal; } .dropdown-menu a:hover { background:#feaa7c; @@ -3510,17 +3519,7 @@ img.thumbnail { width:134px; } /* devices */ -.device-libs-popover { - left:-80px; - z-index:100; -} -.device-libs-item { - display:block; - padding:4px 12px; - white-space:nowrap; - color:#eb8205; -} -.device-libs-item:hover { - background:#f8f8f8; - text-decoration:none; +.device-libs-dropdown-menu { + left:-60px; + min-width:200px; } diff --git a/seahub/templates/js/templates.html b/seahub/templates/js/templates.html index 7ab6655b4f..929ac71bc7 100644 --- a/seahub/templates/js/templates.html +++ b/seahub/templates/js/templates.html @@ -41,21 +41,23 @@
- - +
<%= size_formatted %> @@ -1394,17 +1396,20 @@ <%- device_name %> <%- last_login_ip %> - - <%- synced_repos_length %><% if (synced_repos.length > 0) { %> <% } %> -
-
+ + <% if (synced_repos.length > 0) { %> + + <% } else { %> + 0 + <% } %>
diff --git a/seahub/templates/libraries.html b/seahub/templates/libraries.html index ec62655f3a..c6da23d4b8 100644 --- a/seahub/templates/libraries.html +++ b/seahub/templates/libraries.html @@ -287,6 +287,11 @@ app["pageOptions"] = { file_audit_enabled: {% if file_audit_enabled %} true {% else %} false {% endif %}, cur_note: {% if request.cur_note %} {'id': '{{ request.cur_note.id }}'} {% else %} null {% endif %} }; +app.ui = { + currentDropdown: null, + currentHighlightedItem: null, + freezeItemHightlight: false +}; {% if debug %} diff --git a/static/scripts/app/router.js b/static/scripts/app/router.js index 0f1787261d..8126565910 100644 --- a/static/scripts/app/router.js +++ b/static/scripts/app/router.js @@ -42,9 +42,7 @@ define([ Common.initNoticePopup(); this.sideNavView = new SideNavView(); - app.ui = { - sideNavView: this.sideNavView - }; + app.ui.sideNavView = this.sideNavView; this.dirView = new DirView(); diff --git a/static/scripts/app/views/device.js b/static/scripts/app/views/device.js index 6377f12b8c..d509929fa7 100644 --- a/static/scripts/app/views/device.js +++ b/static/scripts/app/views/device.js @@ -3,33 +3,26 @@ define([ 'underscore', 'backbone', 'common', - 'moment' -], function($, _, Backbone, Common, Moment) { + 'moment', + 'app/views/widgets/hl-item-view', + 'app/views/widgets/dropdown' +], function($, _, Backbone, Common, Moment, HLItemView, DropdownView) { 'use strict'; - var DeviceView = Backbone.View.extend({ + var DeviceView = HLItemView.extend({ tagName: 'tr', template: _.template($('#device-item-tmpl').html()), events: { - 'mouseenter': 'highlight', - 'mouseleave': 'rmHighlight', - 'click .unlink-device': 'unlinkDevice', - 'click .js-toggle-repos': 'toggleSyncedRepos' + 'click .unlink-device': 'unlinkDevice' }, initialize: function() { - $(document).click(function(e) { - var target = e.target || event.srcElement; - if (!$('.js-toggle-repos, .device-libs-popover').is(target)) { - $('.device-libs-popover').addClass('hide'); - $('.dir-icon').removeClass('icon-caret-up').addClass('icon-caret-down'); - } - }); + HLItemView.prototype.initialize.call(this); }, - render: function () { + render: function() { var data = this.model.toJSON(); if (typeof(data['synced_repos']) == 'undefined') { @@ -48,36 +41,13 @@ define([ this.$el.html(this.template(data)); + new DropdownView({ + el: this.$('.js-dropdown') + }); + return this; }, - highlight: function() { - this.$el.addClass('hl'); - this.$el.find('.op-icon').removeClass('vh'); - }, - - rmHighlight: function() { - this.$el.removeClass('hl'); - this.$el.find('.op-icon').addClass('vh'); - }, - - toggleSyncedRepos: function(e) { - var $current_icon= $(e.currentTarget).children('.dir-icon'), - $current_popover = $(e.currentTarget).next('.device-libs-popover'); - - $('.device-libs-popover').not($current_popover).addClass('hide'); - $('.dir-icon').not($current_icon).removeClass('icon-caret-up').addClass('icon-caret-down'); - - $current_popover.toggleClass('hide'); - if ($current_icon.hasClass('icon-caret-up')) { - $current_icon.removeClass('icon-caret-up').addClass('icon-caret-down'); - } else { - $current_icon.removeClass('icon-caret-down').addClass('icon-caret-up'); - } - - return false - }, - unlinkDevice: function() { var _this = this, device_name = this.model.get('device_name'); diff --git a/static/scripts/app/views/group-repo.js b/static/scripts/app/views/group-repo.js index f4d4952321..c4fdeebb87 100644 --- a/static/scripts/app/views/group-repo.js +++ b/static/scripts/app/views/group-repo.js @@ -2,22 +2,23 @@ define([ 'jquery', 'underscore', 'backbone', - 'common' -], function($, _, Backbone, Common) { + 'common', + 'app/views/widgets/hl-item-view' +], function($, _, Backbone, Common, HLItemView) { 'use strict'; - var GroupRepoView = Backbone.View.extend({ + var GroupRepoView = HLItemView.extend({ tagName: 'tr', template: _.template($('#group-repo-tmpl').html()), events: { - 'mouseenter': 'highlight', - 'mouseleave': 'rmHighlight', 'click .cancel-share': 'unshare' }, initialize: function(options) { + HLItemView.prototype.initialize.call(this); + this.group_id = options.group_id; this.is_staff = options.is_staff; @@ -48,14 +49,6 @@ define([ return this; }, - highlight: function() { - this.$el.addClass('hl').find('.op-icon').removeClass('vh'); - }, - - rmHighlight: function() { - this.$el.removeClass('hl').find('.op-icon').addClass('vh'); - }, - unshare: function() { var lib_name = this.model.get('name'); this.model.destroy({ diff --git a/static/scripts/app/views/organization-repo.js b/static/scripts/app/views/organization-repo.js index 8fa7b918e8..19a25d8938 100644 --- a/static/scripts/app/views/organization-repo.js +++ b/static/scripts/app/views/organization-repo.js @@ -2,16 +2,18 @@ define([ 'jquery', 'underscore', 'backbone', - 'common' -], function($, _, Backbone, Common) { + 'common', + 'app/views/widgets/hl-item-view' +], function($, _, Backbone, Common, HLItemView) { 'use strict'; - var OrganizationRepoView = Backbone.View.extend({ + var OrganizationRepoView = HLItemView.extend({ tagName: 'tr', template: _.template($('#organization-repo-tmpl').html()), initialize: function() { + HLItemView.prototype.initialize.call(this); }, render: function() { @@ -27,19 +29,9 @@ define([ }, events: { - 'mouseenter': 'highlight', - 'mouseleave': 'rmHighlight', 'click .cancel-share': 'removeShare' }, - highlight: function() { - this.$el.addClass('hl').find('.op-icon').removeClass('vh'); - }, - - rmHighlight: function() { - this.$el.removeClass('hl').find('.op-icon').addClass('vh'); - }, - removeShare: function() { var el = this.$el; var lib_name = this.model.get('name'); diff --git a/static/scripts/app/views/repo.js b/static/scripts/app/views/repo.js index bc1f10c3c5..e6d189d185 100644 --- a/static/scripts/app/views/repo.js +++ b/static/scripts/app/views/repo.js @@ -7,12 +7,15 @@ define([ 'app/views/dialogs/repo-change-password', 'app/views/dialogs/repo-history-settings', 'app/views/dialogs/repo-share-link-admin', - 'app/views/dialogs/repo-folder-perm-admin' + 'app/views/dialogs/repo-folder-perm-admin', + 'app/views/widgets/hl-item-view', + 'app/views/widgets/dropdown' ], function($, _, Backbone, Common, ShareView, RepoChangePasswordDialog, - HistorySettingsDialog, RepoShareLinkAdminDialog, RepoFolderPermAdminDialog) { + HistorySettingsDialog, RepoShareLinkAdminDialog, RepoFolderPermAdminDialog, + HLItemView, DropdownView) { 'use strict'; - var RepoView = Backbone.View.extend({ + var RepoView = HLItemView.extend({ tagName: 'tr', template: _.template($('#repo-tmpl').html()), @@ -21,11 +24,8 @@ define([ transferTemplate: _.template($('#repo-transfer-form-tmpl').html()), events: { - 'mouseenter': 'highlight', - 'mouseleave': 'rmHighlight', 'click .repo-delete-btn': 'del', 'click .repo-share-btn': 'share', - 'click .js-toggle-popup': 'togglePopup', 'click .js-repo-rename': 'rename', 'click .js-repo-transfer': 'transfer', 'click .js-repo-change-password': 'changePassword', @@ -35,6 +35,8 @@ define([ }, initialize: function() { + HLItemView.prototype.initialize.call(this); + this.listenTo(this.model, "change", this.render); }, @@ -47,26 +49,12 @@ define([ 'icon_title': this.model.getIconTitle() }); this.$el.html(this.template(obj)); + this.dropdown = new DropdownView({ + el: this.$('.js-dropdown') + }); return this; }, - // disable 'hover' when 'repo-del-confirm' popup is shown - highlight: function() { - if ($('#my-own-repos .repo-del-confirm').length == 0 - && !$('.hidden-op:visible').length - && !$('#repo-rename-form').length) { - this.$el.addClass('hl').find('.op-icon').removeClass('vh'); - } - }, - - rmHighlight: function() { - if ($('#my-own-repos .repo-del-confirm').length == 0 - && !$('.hidden-op:visible').length - && !$('#repo-rename-form').length) { - this.$el.removeClass('hl').find('.op-icon').addClass('vh'); - } - }, - del: function() { var del_icon = this.$('.repo-delete-btn'); var op_container = this.$('.op-container').css({'position': 'relative'}); @@ -83,9 +71,12 @@ define([ 'width': 180 }); + app.ui.freezeItemHightlight = true; + var _this = this; $('.no', confirm_popup).click(function() { confirm_popup.addClass('hide').remove(); // `addClass('hide')`: to rm cursor + app.ui.freezeItemHightlight = false; _this.rmHighlight(); }); $('.yes', confirm_popup).click(function() { @@ -95,10 +86,12 @@ define([ dataType: 'json', beforeSend: Common.prepareCSRFToken, success: function(data) { + app.ui.freezeItemHightlight = false; _this.remove(); Common.feedback(gettext("Delete succeeded."), 'success'); }, error: function(xhr) { + app.ui.freezeItemHightlight = false; confirm_popup.addClass('hide').remove(); _this.rmHighlight(); @@ -129,21 +122,7 @@ define([ }, togglePopup: function() { - var $icon = this.$('.js-toggle-popup'), - $popup = this.$('.hidden-op'); - - if ($popup.hasClass('hide')) { // the popup is not shown - $popup.css({'left': $icon.position().left}); - if ($icon.offset().top + $popup.height() <= $('#main').offset().top + $('#main').height()) { - // below the icon - $popup.css('top', $icon.position().top + $icon.outerHeight(true) + 3); - } else { - $popup.css('bottom', $icon.parent().outerHeight() - $icon.position().top + 3); - } - $popup.removeClass('hide'); - } else { - $popup.addClass('hide'); - } + this.dropdown.hide(); }, rename: function() { @@ -164,8 +143,10 @@ define([ $name_span.hide(); this.togglePopup(); + app.ui.freezeItemHightlight = true; var cancelRename = function() { + app.ui.freezeItemHightlight = false; form.remove(); $op_td.show(); $name_span.show(); @@ -195,6 +176,7 @@ define([ repo_id: _this.model.get('id') }) + '?op=rename'; var after_op_success = function(data) { + app.ui.freezeItemHightlight = false; _this.model.set({ 'name': new_name }); // it will trigger 'change' event }; var after_op_error = function(xhr) { diff --git a/static/scripts/app/views/shared-repo.js b/static/scripts/app/views/shared-repo.js index caa04844fc..20d0083a44 100644 --- a/static/scripts/app/views/shared-repo.js +++ b/static/scripts/app/views/shared-repo.js @@ -2,22 +2,22 @@ define([ 'jquery', 'underscore', 'backbone', - 'common' -], function($, _, Backbone, Common) { + 'common', + 'app/views/widgets/hl-item-view' +], function($, _, Backbone, Common, HLItemView) { 'use strict'; - var SharedRepoView = Backbone.View.extend({ + var SharedRepoView = HLItemView.extend({ tagName: 'tr', template: _.template($('#shared-repo-tmpl').html()), events: { - 'mouseenter': 'showAction', - 'mouseleave': 'hideAction', 'click .unshare-btn': 'removeShare' }, initialize: function() { + HLItemView.prototype.initialize.call(this); }, removeShare: function(e) { @@ -56,17 +56,8 @@ define([ }); this.$el.html(this.template(obj)); return this; - }, - - showAction: function() { - this.$el.addClass('hl'); - this.$el.find('.op-icon').removeClass('vh'); - }, - - hideAction: function() { - this.$el.removeClass('hl'); - this.$el.find('.op-icon').addClass('vh'); } + }); return SharedRepoView; diff --git a/static/scripts/app/views/starred-file-item.js b/static/scripts/app/views/starred-file-item.js index d090f5f09c..32cb1cfb3f 100644 --- a/static/scripts/app/views/starred-file-item.js +++ b/static/scripts/app/views/starred-file-item.js @@ -2,22 +2,22 @@ define([ 'jquery', 'underscore', 'backbone', - 'common' -], function($, _, Backbone, Common) { + 'common', + 'app/views/widgets/hl-item-view' +], function($, _, Backbone, Common, HLItemView) { 'use strict'; - var StarredFileView = Backbone.View.extend({ + var StarredFileView = HLItemView.extend({ tagName: 'tr', template: _.template($('#starred-file-item-tmpl').html()), events: { - 'mouseenter': 'showAction', - 'mouseleave': 'hideAction', 'click .unstar': 'removeShare' }, initialize: function() { + HLItemView.prototype.initialize.call(this); }, render: function () { @@ -48,16 +48,6 @@ define([ Common.ajaxErrorHandler(xhr); } }); - }, - - showAction: function() { - this.$el.addClass('hl'); - this.$el.find('.op-icon').removeClass('vh'); - }, - - hideAction: function() { - this.$el.removeClass('hl'); - this.$el.find('.op-icon').addClass('vh'); } }); diff --git a/static/scripts/app/views/widgets/dropdown.js b/static/scripts/app/views/widgets/dropdown.js new file mode 100644 index 0000000000..e6715ccc9a --- /dev/null +++ b/static/scripts/app/views/widgets/dropdown.js @@ -0,0 +1,69 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', +], function($, _, Backbone, Common) { + 'use strict'; + + /* + * Dropdown View. + */ + + // There can be only one visible dropdown view + $(document).click(function(e) { + var view = app.ui.currentDropdown; + var target = e.target || event.srcElement; + + if (!view) { + return true; + } + + if (!view.$('.js-dropdown-content').is(target) + && !view.$('.js-dropdown-content').find('*').is(target)) + { + view.hide(); + if (app.ui.currentHighlightedItem) { + app.ui.currentHighlightedItem.rmHighlight(); + } + } + return true; + }); + + var DropdownView = Backbone.View.extend({ + + toggleClass: '.js-dropdown-toggle', + popupClass: '.js-dropdown-content', + + initialize: function(options) { + this.$el.on('click', '.js-dropdown-toggle', _.bind(this.toggleDropdown, this)); + }, + + hide: function() { + app.ui.currentDropdown = null; + this.$('.js-dropdown-content').addClass('hide'); + }, + + show: function() { + app.ui.currentDropdown = this; + this.$('.js-dropdown-content').removeClass('hide'); + }, + + toggleDropdown: function() { + if (app.ui.currentDropdown && app.ui.currentDropdown != this) { + app.ui.currentDropdown.hide(); + } + + if (this.$('.js-dropdown-content').is(':hidden')) { + this.show(); + } else { + this.hide(); + } + + return false; + } + + }); + + return DropdownView; +}); diff --git a/static/scripts/app/views/widgets/hl-item-view.js b/static/scripts/app/views/widgets/hl-item-view.js new file mode 100644 index 0000000000..59d806e08a --- /dev/null +++ b/static/scripts/app/views/widgets/hl-item-view.js @@ -0,0 +1,42 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', +], function($, _, Backbone, Common) { + 'use strict'; + + /* + * Hightable Item View. + */ + var HLItemView = Backbone.View.extend({ + tagName: 'tr', + + hiddenOperationClass: '.op-icon', + + initialize: function(options) { + this.$el.on('mouseenter', _.bind(this.highlight, this)); + this.$el.on('mouseleave', _.bind(this.rmHighlight, this)); + }, + + highlight: function() { + // if there are dropdown items or freezeItemHightlight is set, don't highlight + if (app.ui.currentDropdown || app.ui.freezeItemHightlight) { + return; + } + app.ui.currentHighlightedItem = this; + this.$el.addClass('hl').find(this.hiddenOperationClass).removeClass('vh'); + }, + + rmHighlight: function() { + if (app.ui.currentDropdown || app.ui.freezeItemHightlight) { + return; + } + app.ui.currentHighlightedItem = null; + this.$el.removeClass('hl').find(this.hiddenOperationClass).addClass('vh'); + } + + }); + + return HLItemView; +});