From 5457684975794055fe8c914d8f1df0177be788d7 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 26 Aug 2015 15:51:56 -0400 Subject: [PATCH] Split `hamburger-menu` out into `menu-panel` --- .../components/hamburger-menu.js.es6 | 95 +----------- .../discourse/components/menu-panel.js.es6 | 124 +++++++++++++++ .../discourse/templates/application.hbs | 2 +- .../templates/components/hamburger-menu.hbs | 141 ++++++++---------- .../templates/components/menu-panel.hbs | 10 ++ .../discourse/templates/header.hbs | 3 +- .../base/{hamburger.scss => menu-panel.scss} | 28 ++-- app/assets/stylesheets/desktop.scss | 2 +- .../{hamburger.scss => menu-panel.scss} | 0 .../{hamburger.scss => menu-panel.scss} | 0 .../hamburger-menu-staff-test.js.es6 | 6 +- .../acceptance/hamburger-menu-test.js.es6 | 36 ++--- .../components/menu-panel-test.js.es6 | 80 ++++++++++ .../javascripts/helpers/component-test.js.es6 | 5 + 14 files changed, 316 insertions(+), 216 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/menu-panel.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/components/menu-panel.hbs rename app/assets/stylesheets/common/base/{hamburger.scss => menu-panel.scss} (89%) rename app/assets/stylesheets/desktop/{hamburger.scss => menu-panel.scss} (100%) rename app/assets/stylesheets/mobile/{hamburger.scss => menu-panel.scss} (100%) create mode 100644 test/javascripts/components/menu-panel-test.js.es6 diff --git a/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 b/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 index 183df9350db..903c705c61c 100644 --- a/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 +++ b/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 @@ -1,67 +1,6 @@ -import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators'; - - +import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Component.extend({ - classNameBindings: ['visible::hidden', 'viewMode'], - attributeBindings: ['style'], - elementId: 'hamburger-menu', - viewMode: 'dropDown', - - showClose: Ember.computed.equal('viewMode', 'slide-in'), - - @computed('viewMode') - style(viewMode) { - if (viewMode === 'drop-down') { - const $buttonPanel = $('header ul.icons'); - - const buttonPanelPos = $buttonPanel.offset(); - const myWidth = this.$().width(); - - const posTop = parseInt(buttonPanelPos.top + $buttonPanel.height()); - const posLeft = parseInt(buttonPanelPos.left + $buttonPanel.width() - myWidth); - - return `left: ${posLeft}px; top: ${posTop}px`.htmlSafe(); - } else { - const headerHeight = parseInt($('header.d-header').height() + 3); - return `top: ${headerHeight}px`.htmlSafe(); - } - }, - - @computed('viewMode') - bodyStyle(viewMode) { - if (viewMode === 'drop-down') { - const height = parseInt($(window).height() * 0.8) - return `height: ${height}px`.htmlSafe(); - } - }, - - @observes('visible') - _visibleChanged() { - const isDropdown = (this.get('viewMode') === 'drop-down'); - if (this.get('visible')) { - - if (isDropdown) { - $('.hamburger-dropdown').addClass('active'); - } - - if ($(window).width() < 1024) { - this.set('viewMode', 'slide-in'); - } else { - this.set('viewMode', 'drop-down'); - } - - $('html').on('click.close-hamburger', (e) => { - const $target = $(e.target); - if ($target.closest('.hamburger-dropdown').length > 0) { return; } - if ($target.closest('#hamburger-menu').length > 0) { return; } - this.hide(); - }); - - } else { - $('.hamburger-dropdown').removeClass('active'); - $('html').off('click.close-hamburger'); - } - }, + classNames: ['hamburger-panel'], @computed() showKeyboardShortcuts() { @@ -83,29 +22,6 @@ export default Ember.Component.extend({ return this.siteSettings.faq_url ? this.siteSettings.faq_url : Discourse.getURL('/faq'); }, - @on('didInsertElement') - _bindEvents() { - this.$().on('click.discourse-hamburger', 'a', () => { - this.hide(); - }); - - this.appEvents.on('dropdowns:closeAll', this, this.hide); - - $('body').on('keydown.discourse-hambuger', (e) => { - if (e.which === 27) { - this.hide(); - } - }); - }, - - @on('willDestroyElement') - _removeEvents() { - this.appEvents.off('dropdowns:closeAll', this, this.hide); - this.$().off('click.discourse-hamburger'); - $('body').off('keydown.discourse-hambuger'); - $('html').off('click.close-hamburger'); - }, - @computed() categories() { const hideUncategorized = !this.siteSettings.allow_uncategorized_topics; @@ -119,14 +35,7 @@ export default Ember.Component.extend({ }); }, - hide() { - this.set('visible', false); - }, - actions: { - close() { - this.hide(); - }, keyboardShortcuts() { this.sendAction('showKeyboardAction'); }, diff --git a/app/assets/javascripts/discourse/components/menu-panel.js.es6 b/app/assets/javascripts/discourse/components/menu-panel.js.es6 new file mode 100644 index 00000000000..962967f82d6 --- /dev/null +++ b/app/assets/javascripts/discourse/components/menu-panel.js.es6 @@ -0,0 +1,124 @@ +import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators'; + +export default Ember.Component.extend({ + classNameBindings: [':menu-panel', 'visible::hidden', 'viewMode'], + attributeBindings: ['style'], + viewMode: 'dropDown', + + showClose: Ember.computed.equal('viewMode', 'slide-in'), + + @computed('viewMode') + style(viewMode) { + if (viewMode === 'drop-down') { + const $buttonPanel = $('header ul.icons'); + if ($buttonPanel.length === 0) { return; } + + const buttonPanelPos = $buttonPanel.offset(); + const myWidth = this.$().width(); + + const posTop = parseInt(buttonPanelPos.top + $buttonPanel.height()) - $(window).scrollTop(); + const posLeft = parseInt(buttonPanelPos.left + $buttonPanel.width() - myWidth); + + return `left: ${posLeft}px; top: ${posTop}px`.htmlSafe(); + } else { + const headerHeight = parseInt($('header.d-header').height() + 3); + return `top: ${headerHeight}px`.htmlSafe(); + } + }, + + @computed('viewMode') + bodyStyle(viewMode) { + if (viewMode === 'drop-down') { + const height = parseInt($(window).height() * 0.8) + return `height: ${height}px`.htmlSafe(); + } + }, + + @observes('visible') + _visibleChanged() { + const force = this.get('force'); + if (force) { + this.set('viewMode', force); + } else if ($(window).width() < 1024) { + this.set('viewMode', 'slide-in'); + } else { + this.set('viewMode', 'drop-down'); + } + + const isDropdown = (this.get('viewMode') === 'drop-down'); + const markActive = this.get('markActive'); + + if (this.get('visible')) { + + 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('.menu-panel').length > 0) { return; } + this.hide(); + }); + + } else { + if (markActive) { + $(markActive).removeClass('active'); + } + $('html').off('click.close-menu-panel'); + } + }, + + @computed() + showKeyboardShortcuts() { + return !Discourse.Mobile.mobileView && !this.capabilities.touch; + }, + + @computed() + showMobileToggle() { + return Discourse.Mobile.mobileView || (this.siteSettings.enable_mobile_theme && this.capabilities.touch); + }, + + @computed() + mobileViewLinkTextKey() { + return Discourse.Mobile.mobileView ? "desktop_view" : "mobile_view"; + }, + + @computed() + faqUrl() { + return this.siteSettings.faq_url ? this.siteSettings.faq_url : Discourse.getURL('/faq'); + }, + + @on('didInsertElement') + _bindEvents() { + this.$().on('click.discourse-menu-panel', 'a', () => { + this.hide(); + }); + + this.appEvents.on('dropdowns:closeAll', this, this.hide); + + $('body').on('keydown.discourse-menu-panel', (e) => { + if (e.which === 27) { + this.hide(); + } + }); + }, + + @on('willDestroyElement') + _removeEvents() { + this.appEvents.off('dropdowns:closeAll', this, this.hide); + this.$().off('click.discourse-menu-panel'); + $('body').off('keydown.discourse-menu-panel'); + $('html').off('click.close-menu-panel'); + }, + + hide() { + this.set('visible', false); + }, + + actions: { + close() { + this.hide(); + } + } +}); diff --git a/app/assets/javascripts/discourse/templates/application.hbs b/app/assets/javascripts/discourse/templates/application.hbs index 24926f44871..53cf8fd5c09 100644 --- a/app/assets/javascripts/discourse/templates/application.hbs +++ b/app/assets/javascripts/discourse/templates/application.hbs @@ -1,5 +1,5 @@ {{render "header"}} -{{hamburger-menu visible=hamburgerVisible showKeyboardAction="showKeyboardShortcutsHelp"}} +{{hamburger-menu hamburgerVisible=hamburgerVisible showKeyboardAction="showKeyboardShortcutsHelp"}}
diff --git a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs index 474e3a3cd24..d92a9cbfe06 100644 --- a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs +++ b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs @@ -1,82 +1,73 @@ -{{#if visible}} - {{#if showClose}} - - {{/if}} - -
-
-{{/if}} + {{#if siteSettings.enable_user_directory}} +
  • {{#link-to 'users' class="user-directory-link"}}{{i18n "directory.title"}}{{/link-to}}
  • + {{/if}} + + {{#if currentUser.show_queued_posts}} +
  • + {{#link-to 'queued-posts'}} + {{i18n "queue.title"}} + {{#if currentUser.post_queue_new_count}} + {{currentUser.post_queue_new_count}} + {{/if}} + {{/link-to}} +
  • + {{/if}} + + {{plugin-outlet "site-map-links"}} + + {{#if showKeyboardShortcuts}} +
  • {{i18n 'keyboard_shortcuts_help.title'}}
  • + {{/if}} +
  • + {{i18n 'faq'}} +
  • +
  • + {{#link-to 'about' class="about-link"}}{{i18n 'about.simple_title'}}{{/link-to}} +
  • + {{#if showMobileToggle}} +
  • {{boundI18n mobileViewLinkTextKey}}
  • + {{/if}} + + {{plugin-outlet "site-map-links-last"}} + + + {{#if categories}} + + {{/if}} +{{/menu-panel}} diff --git a/app/assets/javascripts/discourse/templates/components/menu-panel.hbs b/app/assets/javascripts/discourse/templates/components/menu-panel.hbs new file mode 100644 index 00000000000..2a708126447 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/menu-panel.hbs @@ -0,0 +1,10 @@ +{{#if visible}} + {{#if showClose}} + + {{/if}} +
    + {{yield}} +
    +{{/if}} diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index 6d833d3f321..7831fe9c61a 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -1,6 +1,5 @@
    - {{home-logo minimized=showExtraInfo}}
    @@ -52,7 +51,7 @@ {{fa-icon "bars"}} {{else}} - diff --git a/app/assets/stylesheets/common/base/hamburger.scss b/app/assets/stylesheets/common/base/menu-panel.scss similarity index 89% rename from app/assets/stylesheets/common/base/hamburger.scss rename to app/assets/stylesheets/common/base/menu-panel.scss index ce25b4ec815..0c8243d0ba7 100644 --- a/app/assets/stylesheets/common/base/hamburger.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -1,20 +1,20 @@ -#hamburger-menu.slide-in { +.menu-panel.slide-in { position: fixed; right: 0; top: 0; height: 100%; - .hamburger-body { + .panel-body { position: absolute; top: 40px; bottom: 37px; } } -#hamburger-menu.drop-down { - position: absolute; +.menu-panel.drop-down { + position: fixed; } -#hamburger-menu { +.menu-panel { border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); box-shadow: 0 2px 2px rgba(0,0,0, .25); background-color: $secondary; @@ -23,7 +23,7 @@ padding: 0.5em 0.5em 0.5em 0.5em; width: 300px; - .close-hamburger { + .close-panel { float: right; color: dark-light-choose(scale-color($header_primary, $lightness: 50%), $header_primary); font-size: 1.5em; @@ -33,19 +33,24 @@ z-index: 9999; } - .hamburger-header { + .panel-header { position: absolute; right: 20px; } - - ul { list-style: none; margin: 0; padding: 0; } + .panel-body { + overflow-y: auto; + overflow-x: hidden; + } +} + +.hamburger-panel { ul.location-links li, li.heading { a { padding: 0.5em; @@ -76,10 +81,5 @@ font-weight: normal; font-size: 11px; } - - .hamburger-body { - overflow-y: auto; - overflow-x: hidden; - } } diff --git a/app/assets/stylesheets/desktop.scss b/app/assets/stylesheets/desktop.scss index 929f4727b93..e3a79437d25 100644 --- a/app/assets/stylesheets/desktop.scss +++ b/app/assets/stylesheets/desktop.scss @@ -17,7 +17,7 @@ @import "desktop/user"; @import "desktop/history"; @import "desktop/queued-posts"; -@import "desktop/hamburger"; +@import "desktop/menu-panel"; /* These files doesn't actually exist, they are injected by DiscourseSassImporter. */ diff --git a/app/assets/stylesheets/desktop/hamburger.scss b/app/assets/stylesheets/desktop/menu-panel.scss similarity index 100% rename from app/assets/stylesheets/desktop/hamburger.scss rename to app/assets/stylesheets/desktop/menu-panel.scss diff --git a/app/assets/stylesheets/mobile/hamburger.scss b/app/assets/stylesheets/mobile/menu-panel.scss similarity index 100% rename from app/assets/stylesheets/mobile/hamburger.scss rename to app/assets/stylesheets/mobile/menu-panel.scss diff --git a/test/javascripts/acceptance/hamburger-menu-staff-test.js.es6 b/test/javascripts/acceptance/hamburger-menu-staff-test.js.es6 index 21940146fa7..d4bdd05f6f0 100644 --- a/test/javascripts/acceptance/hamburger-menu-staff-test.js.es6 +++ b/test/javascripts/acceptance/hamburger-menu-staff-test.js.es6 @@ -6,8 +6,8 @@ test("Menu Items", (assert) => { visit("/"); click("#toggle-hamburger-menu"); andThen(() => { - assert.ok(exists("#hamburger-menu .admin-link")); - assert.ok(exists("#hamburger-menu .flagged-posts-link")); - assert.ok(exists("#hamburger-menu .flagged-posts.badge-notification"), "it displays flag notifications"); + assert.ok(exists(".hamburger-panel .admin-link")); + assert.ok(exists(".hamburger-panel .flagged-posts-link")); + assert.ok(exists(".hamburger-panel .flagged-posts.badge-notification"), "it displays flag notifications"); }); }); diff --git a/test/javascripts/acceptance/hamburger-menu-test.js.es6 b/test/javascripts/acceptance/hamburger-menu-test.js.es6 index 0b135767fd3..421acf9165a 100644 --- a/test/javascripts/acceptance/hamburger-menu-test.js.es6 +++ b/test/javascripts/acceptance/hamburger-menu-test.js.es6 @@ -2,38 +2,20 @@ import { acceptance } from "helpers/qunit-helpers"; acceptance("Hamburger Menu"); -test("Toggle Menu", (assert) => { - visit("/"); - andThen(() => { - assert.ok(exists("#hamburger-menu.hidden"), "hidden by default"); - }); - - click("#toggle-hamburger-menu"); - andThen(() => { - assert.ok(!exists("#hamburger-menu.hidden"), "a click makes it appear"); - }); - - click('#main-outlet') - andThen(() => { - assert.ok(exists("#hamburger-menu.hidden"), "clicking the body hides the menu"); - }); - -}); - test("Menu Items", (assert) => { visit("/"); click("#toggle-hamburger-menu"); andThen(() => { - assert.ok(!exists("#hamburger-menu .admin-link"), 'does not have admin link'); - assert.ok(!exists("#hamburger-menu .flagged-posts-link"), 'does not have flagged posts link'); + assert.ok(!exists(".hamburger-panel .admin-link"), 'does not have admin link'); + assert.ok(!exists(".hamburger-panel .flagged-posts-link"), 'does not have flagged posts link'); - assert.ok(exists("#hamburger-menu .latest-topics-link"), 'last link to latest'); - assert.ok(exists("#hamburger-menu .badge-link"), 'has link to badges'); - assert.ok(exists("#hamburger-menu .user-directory-link"), 'has user directory link'); - assert.ok(exists("#hamburger-menu .faq-link"), 'has faq link'); - assert.ok(exists("#hamburger-menu .about-link"), 'has about link'); - assert.ok(exists("#hamburger-menu .categories-link"), 'has categories link'); + assert.ok(exists(".hamburger-panel .latest-topics-link"), 'last link to latest'); + assert.ok(exists(".hamburger-panel .badge-link"), 'has link to badges'); + assert.ok(exists(".hamburger-panel .user-directory-link"), 'has user directory link'); + assert.ok(exists(".hamburger-panel .faq-link"), 'has faq link'); + assert.ok(exists(".hamburger-panel .about-link"), 'has about link'); + assert.ok(exists(".hamburger-panel .categories-link"), 'has categories link'); - assert.ok(exists('#hamburger-menu .category-link'), 'has at least one category'); + assert.ok(exists('.hamburger-panel .category-link'), 'has at least one category'); }); }); diff --git a/test/javascripts/components/menu-panel-test.js.es6 b/test/javascripts/components/menu-panel-test.js.es6 new file mode 100644 index 00000000000..98cc5631761 --- /dev/null +++ b/test/javascripts/components/menu-panel-test.js.es6 @@ -0,0 +1,80 @@ +import componentTest from 'helpers/component-test'; +moduleForComponent('menu-panel', {integration: true}); + +componentTest('as a dropdown', { + template: ` +
    click me
    + + + + {{#menu-panel visible=panelVisible markActive=".menu-selected" force="drop-down"}} + Some content + {{/menu-panel}} + `, + + setup() { + this.set('panelVisible', false); + }, + + test(assert) { + assert.ok(exists(".menu-panel.hidden"), "hidden by default"); + assert.ok(!exists(".menu-selected.active"), "does not mark anything as active"); + + this.set('panelVisible', true); + andThen(() => { + assert.ok(!exists('.menu-panel .close-panel'), "the close X is not shown"); + assert.ok(!exists(".menu-panel.hidden"), "toggling visible makes it appear"); + assert.ok(exists(".menu-selected.active"), "marks the panel as active"); + }); + + click('#outside-area') + andThen(() => { + assert.ok(exists(".menu-panel.hidden"), "clicking the body hides the menu"); + assert.ok(!exists(".menu-selected.active"), "removes the active class"); + assert.equal(this.get('panelVisible'), false, 'it updates the bound variable'); + }); + } +}); + +componentTest('as a slide-in', { + template: ` +
    click me
    + + + {{#menu-panel visible=panelVisible markActive=".menu-selected" force="slide-in"}} + Some content + {{/menu-panel}} + `, + + setup() { + this.set('panelVisible', false); + }, + + test(assert) { + assert.ok(exists(".menu-panel.hidden"), "hidden by default"); + assert.ok(!exists(".menu-selected.active"), "does not mark anything as active"); + + this.set('panelVisible', true); + andThen(() => { + assert.ok(!exists(".menu-panel.hidden"), "toggling visible makes it appear"); + assert.ok(!exists(".menu-selected.active"), "slide ins don't mark as active"); + }); + + click('#outside-area') + andThen(() => { + assert.ok(exists(".menu-panel.hidden"), "clicking the body hides the menu"); + assert.equal(this.get('panelVisible'), false, 'it updates the bound variable'); + this.set('panelVisible', true); + }); + + andThen(() => { + assert.ok(exists('.menu-panel .close-panel'), "the close X is shown"); + }); + + click('.close-panel'); + andThen(() => { + assert.ok(exists(".menu-panel.hidden"), "clicking the close button closes it"); + assert.equal(this.get('panelVisible'), false, 'it updates the bound variable'); + }); + } +}); diff --git a/test/javascripts/helpers/component-test.js.es6 b/test/javascripts/helpers/component-test.js.es6 index f6cb5c248f8..f2ec007a4df 100644 --- a/test/javascripts/helpers/component-test.js.es6 +++ b/test/javascripts/helpers/component-test.js.es6 @@ -1,3 +1,4 @@ +import AppEvents from 'discourse/lib/app-events'; import createStore from 'helpers/create-store'; export default function(name, opts) { @@ -8,8 +9,12 @@ export default function(name, opts) { const store = createStore(); opts.setup.call(this, store); } + const appEvents = AppEvents.create(); + this.container.register('site-settings:main', Discourse.SiteSettings, { instantiate: false }); + this.container.register('app-events:main', appEvents, { instantiate: false }); this.container.injection('component', 'siteSettings', 'site-settings:main'); + this.container.injection('component', 'appEvents', 'app-events:main'); andThen(() => { this.render(opts.template);