From 3ae5a0a2dfacaf9422603bd10a17143a0a5c3af3 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 28 Aug 2015 14:32:53 -0400 Subject: [PATCH] UX: Merge notifications and user dropdown --- .../conditional-loading-spinner.js.es6 | 4 +- .../components/header-dropdown.js.es6 | 17 +++ .../discourse/components/menu-panel.js.es6 | 12 +- .../discourse/components/search-menu.js.es6 | 2 +- .../discourse/components/user-menu.js.es6 | 66 ++++++++++ .../discourse/controllers/header.js.es6 | 61 +-------- .../controllers/notifications.js.es6 | 7 - .../controllers/user-dropdown.js.es6 | 25 ---- .../discourse/routes/discourse.js.es6 | 1 - .../javascripts/discourse/routes/topic.js.es6 | 7 - .../templates/components/hamburger-menu.hbs | 4 +- .../templates/components/header-dropdown.hbs | 9 ++ .../templates/components/search-menu.hbs | 6 +- .../user-menu.hbs} | 23 +++- .../discourse/templates/header.hbs | 90 +++++-------- .../discourse/templates/notifications.hbs | 16 --- .../javascripts/discourse/views/header.js.es6 | 123 ------------------ .../stylesheets/common/base/header.scss | 92 +------------ .../stylesheets/common/base/menu-panel.scss | 60 ++++++++- .../common/components/badges.css.scss | 2 +- app/assets/stylesheets/desktop/header.scss | 36 ----- app/assets/stylesheets/mobile/header.scss | 46 ------- .../acceptance/header-test-staff.js.es6 | 12 +- .../components/menu-panel-test.js.es6 | 5 - .../javascripts/controllers/topic-test.js.es6 | 2 +- .../controllers/user-dropdown-test.js.es6 | 10 -- 26 files changed, 211 insertions(+), 527 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/header-dropdown.js.es6 create mode 100644 app/assets/javascripts/discourse/components/user-menu.js.es6 delete mode 100644 app/assets/javascripts/discourse/controllers/notifications.js.es6 delete mode 100644 app/assets/javascripts/discourse/controllers/user-dropdown.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/components/header-dropdown.hbs rename app/assets/javascripts/discourse/templates/{user-dropdown.hbs => components/user-menu.hbs} (56%) delete mode 100644 app/assets/javascripts/discourse/templates/notifications.hbs delete mode 100644 test/javascripts/controllers/user-dropdown-test.js.es6 diff --git a/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6 b/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6 index 807990d707a..49e2eefca14 100644 --- a/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6 +++ b/app/assets/javascripts/discourse/components/conditional-loading-spinner.js.es6 @@ -1,8 +1,8 @@ export default Ember.Component.extend({ - classNameBindings: ['containerClass'], + classNameBindings: ['containerClass', 'condition:visible'], containerClass: function() { - return (this.get('size') === 'small') ? 'inline-spinner' : undefined; + return this.get('size') === 'small' ? 'inline-spinner' : undefined; }.property('size'), render: function(buffer) { diff --git a/app/assets/javascripts/discourse/components/header-dropdown.js.es6 b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 new file mode 100644 index 00000000000..4990aa107a1 --- /dev/null +++ b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 @@ -0,0 +1,17 @@ +export default Ember.Component.extend({ + tagName: 'li', + classNameBindings: [':header-dropdown-toggle', 'active'], + + active: Ember.computed.alias('toggleVisible'), + + actions: { + toggle() { + if (this.siteSettings.login_required && !this.currentUser) { + this.sendAction('loginAction'); + } else { + this.toggleProperty('toggleVisible'); + } + this.appEvents.trigger('dropdowns:closeAll'); + } + } +}); diff --git a/app/assets/javascripts/discourse/components/menu-panel.js.es6 b/app/assets/javascripts/discourse/components/menu-panel.js.es6 index 3333ffa2ab4..190834d26ea 100644 --- a/app/assets/javascripts/discourse/components/menu-panel.js.es6 +++ b/app/assets/javascripts/discourse/components/menu-panel.js.es6 @@ -55,21 +55,14 @@ export default Ember.Component.extend({ @observes('viewMode', 'visible') _visibleChanged() { const isDropdown = (this.get('viewMode') === 'drop-down'); - const markActive = this.get('markActive'); - if (this.get('visible')) { this.appEvents.on('dropdowns:closeAll', this, this.hide); // Allow us to hook into things being shown Ember.run.scheduleOnce('afterRender', () => this.sendAction('onVisible')); - - if (isDropdown && markActive) { - $(markActive).addClass('active'); - } - $('html').on('click.close-menu-panel', (e) => { const $target = $(e.target); - if ($target.closest(markActive).length > 0) { return; } + if ($target.closest('.header-dropdown-toggle').length > 0) { return; } if ($target.closest('.menu-panel').length > 0) { return; } this.hide(); }); @@ -77,9 +70,6 @@ export default Ember.Component.extend({ this._watchSizeChanges(); } else { Ember.run.scheduleOnce('afterRender', () => this.sendAction('onHidden')); - if (markActive) { - $(markActive).removeClass('active'); - } $('html').off('click.close-menu-panel'); this._stopWatchingSize(); } diff --git a/app/assets/javascripts/discourse/components/search-menu.js.es6 b/app/assets/javascripts/discourse/components/search-menu.js.es6 index e8e85b34920..4786747d99f 100644 --- a/app/assets/javascripts/discourse/components/search-menu.js.es6 +++ b/app/assets/javascripts/discourse/components/search-menu.js.es6 @@ -167,7 +167,7 @@ export default Ember.Component.extend({ keyDown(e) { const term = this.get('searchService.term'); if (e.which === 13 && term && term.length >= this.siteSettings.min_search_term_length) { - this.set('searchVisible', false); + this.set('visible', false); this.send('fullSearch'); } } diff --git a/app/assets/javascripts/discourse/components/user-menu.js.es6 b/app/assets/javascripts/discourse/components/user-menu.js.es6 new file mode 100644 index 00000000000..5b120046651 --- /dev/null +++ b/app/assets/javascripts/discourse/components/user-menu.js.es6 @@ -0,0 +1,66 @@ +import { url } from 'discourse/lib/computed'; +import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; + +export default Ember.Component.extend({ + classNames: ['user-menu'], + notifications: null, + loadingNotifications: false, + myNotificationsUrl: url('/my/notifications'), + + @observes('visible') + _loadNotifications(visible) { + if (visible) { + this.refreshNotifications(); + } + }, + + @observes('currentUser.lastNotificationChange') + _resetCachedNotifications() { + const visible = this.get('visible'); + + if (!Discourse.get("hasFocus")) { + this.set('visible', false); + this.set('notifications', null); + return; + } + + if (visible) { + this.refreshNotifications(); + } else { + this.set('notifications', null); + } + }, + + refreshNotifications() { + if (this.get('loadingNotifications')) { return; } + this.set("loadingNotifications", true); + + // TODO: It's a bit odd to use the store in a component, but this one really + // wants to reach out and grab notifications + const store = this.container.lookup('store:main'); + store.find('notification', {recent: true}).then((notifications) => { + this.setProperties({ 'currentUser.unread_notifications': 0, notifications }); + }).catch(() => { + this.set('notifications', null); + }).finally(() => { + this.set("loadingNotifications", false); + }); + }, + + @computed() + allowAnon() { + return this.siteSettings.allow_anonymous_posting && + (this.get("currentUser.trust_level") >= this.siteSettings.anonymous_posting_min_trust_level || + this.get("isAnon")); + }, + + isAnon: Ember.computed.alias('currentUser.is_anonymous'), + + actions: { + toggleAnon() { + Discourse.ajax("/users/toggle-anon", {method: 'POST'}).then(function(){ + window.location.reload(); + }); + } + } +}); diff --git a/app/assets/javascripts/discourse/controllers/header.js.es6 b/app/assets/javascripts/discourse/controllers/header.js.es6 index f1749fc7c43..f81ec83cdd2 100644 --- a/app/assets/javascripts/discourse/controllers/header.js.es6 +++ b/app/assets/javascripts/discourse/controllers/header.js.es6 @@ -1,13 +1,11 @@ const HeaderController = Ember.Controller.extend({ topic: null, showExtraInfo: null, - notifications: null, - loadingNotifications: false, hamburgerVisible: false, searchVisible: false, + userMenuVisible: false, needs: ['application'], - loginRequired: Em.computed.alias('controllers.application.loginRequired'), canSignUp: Em.computed.alias('controllers.application.canSignUp'), showSignUpButton: function() { @@ -18,70 +16,13 @@ const HeaderController = Ember.Controller.extend({ return Discourse.User.current() && !this.get('topic.isPrivateMessage'); }.property('topic.isPrivateMessage'), - _resetCachedNotifications: function() { - // a bit hacky, but if we have no focus, hide notifications first - const visible = $("#notifications-dropdown").is(":visible"); - - if(!Discourse.get("hasFocus")) { - if(visible){ - $("html").click(); - } - this.set("notifications", null); - return; - } - if(visible){ - this.refreshNotifications(); - } else { - this.set("notifications", null); - } - }.observes("currentUser.lastNotificationChange"), - - refreshNotifications: function(){ - const self = this; - if (self.get("loadingNotifications")) { return; } - - self.set("loadingNotifications", true); - - this.store.find('notification', {recent: true}).then(function(notifications) { - self.setProperties({ - 'currentUser.unread_notifications': 0, - notifications - }); - }).catch(function() { - self.setProperties({ - notifications: null - }); - }).finally(function() { - self.set("loadingNotifications", false); - }); - }, actions: { toggleStar() { const topic = this.get('topic'); if (topic) topic.toggleStar(); return false; - }, - - showNotifications(headerView) { - const self = this; - - if (self.get('currentUser.unread_notifications') || self.get('currentUser.unread_private_messages') || !self.get('notifications')) { - self.refreshNotifications(); - } - headerView.showDropdownBySelector("#user-notifications"); - }, - - toggleSearchMenu() { - this.appEvents.trigger('dropdowns:closeAll'); - this.toggleProperty('searchVisible'); - }, - - toggleHamburgerMenu() { - this.appEvents.trigger('dropdowns:closeAll'); - this.toggleProperty('hamburgerVisible'); } - } }); diff --git a/app/assets/javascripts/discourse/controllers/notifications.js.es6 b/app/assets/javascripts/discourse/controllers/notifications.js.es6 deleted file mode 100644 index e76d84e968e..00000000000 --- a/app/assets/javascripts/discourse/controllers/notifications.js.es6 +++ /dev/null @@ -1,7 +0,0 @@ -import { url } from 'discourse/lib/computed'; - -export default Ember.ArrayController.extend({ - needs: ['header'], - loadingNotifications: Em.computed.alias('controllers.header.loadingNotifications'), - myNotificationsUrl: url('/my/notifications') -}); diff --git a/app/assets/javascripts/discourse/controllers/user-dropdown.js.es6 b/app/assets/javascripts/discourse/controllers/user-dropdown.js.es6 deleted file mode 100644 index b249ea15047..00000000000 --- a/app/assets/javascripts/discourse/controllers/user-dropdown.js.es6 +++ /dev/null @@ -1,25 +0,0 @@ -export default Ember.ArrayController.extend({ - showAdminLinks: Em.computed.alias("currentUser.staff"), - - allowAnon: function(){ - return this.siteSettings.allow_anonymous_posting && - (this.get("currentUser.trust_level") >= this.siteSettings.anonymous_posting_min_trust_level || - this.get("isAnon")); - }.property(), - - isAnon: function(){ - return this.get("currentUser.is_anonymous"); - }.property(), - - actions: { - logout() { - Discourse.logout(); - return false; - }, - toggleAnon() { - Discourse.ajax("/users/toggle-anon", {method: 'POST'}).then(function(){ - window.location.reload(); - }); - } - } -}); diff --git a/app/assets/javascripts/discourse/routes/discourse.js.es6 b/app/assets/javascripts/discourse/routes/discourse.js.es6 index 20ce38f40a3..cf7a7c27a43 100644 --- a/app/assets/javascripts/discourse/routes/discourse.js.es6 +++ b/app/assets/javascripts/discourse/routes/discourse.js.es6 @@ -77,7 +77,6 @@ export function cleanDOM() { $('.profiler-results .profiler-result').remove(); // Close some elements that may be open - $('.d-dropdown').hide(); $('header ul.icons li').removeClass('active'); $('[data-toggle="dropdown"]').parent().removeClass('open'); // close the lightbox diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6 index 5384dd3d211..5c9a97733a6 100644 --- a/app/assets/javascripts/discourse/routes/topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic.js.es6 @@ -203,13 +203,6 @@ const TopicRoute = Discourse.Route.extend({ // In case we navigate from one topic directly to another isTransitioning = false; - if (Discourse.Mobile.mobileView) { - // close the dropdowns on mobile - $('.d-dropdown').hide(); - $('header ul.icons li').removeClass('active'); - $('[data-toggle="dropdown"]').parent().removeClass('open'); - } - controller.setProperties({ model, editingTopic: false, diff --git a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs index d92a9cbfe06..098d310157c 100644 --- a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs +++ b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs @@ -1,5 +1,5 @@ -{{#menu-panel visible=hamburgerVisible markActive=".hamburger-dropdown"}} -