From 1bf0b2a5f44ec0c8d62fe62285f9cd20364cbcfb Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 19 Oct 2016 14:29:43 +1100 Subject: [PATCH] FEATURE: show timeline component when expanding post progress - Show fullscreen timeline with title of topic in mobile - Go to post # kb shortcut now unconditionally uses a modal - Always show wrench on topics (was missing if progress bar was showing) - Be smarter about rendering timeline even if composer is open (provided there is room) --- .../components/topic-navigation.js.es6 | 107 ++++++++++++++++-- .../components/topic-progress.js.es6 | 106 +++-------------- .../components/topic-timeline.js.es6 | 36 +++++- .../discourse/controllers/topic.js.es6 | 2 + .../discourse/lib/keyboard-shortcuts.js.es6 | 6 +- .../templates/components/topic-progress.hbs | 41 ++----- .../javascripts/discourse/templates/topic.hbs | 25 ++-- .../discourse/widgets/topic-timeline.js.es6 | 26 ++++- app/assets/stylesheets/common.scss | 1 + .../{desktop => common}/topic-timeline.scss | 33 ++++-- app/assets/stylesheets/desktop.scss | 1 - vendor/assets/javascripts/div_resizer.js | 1 + 12 files changed, 222 insertions(+), 163 deletions(-) rename app/assets/stylesheets/{desktop => common}/topic-timeline.scss (83%) diff --git a/app/assets/javascripts/discourse/components/topic-navigation.js.es6 b/app/assets/javascripts/discourse/components/topic-navigation.js.es6 index 945d14420e0..72313abb533 100644 --- a/app/assets/javascripts/discourse/components/topic-navigation.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-navigation.js.es6 @@ -1,17 +1,65 @@ +import { observes } from 'ember-addons/ember-computed-decorators'; + export default Ember.Component.extend({ composerOpen: null, - classNameBindings: ['composerOpen'], - showTimeline: null, - info: null, + info: Em.Object.create(), _checkSize() { - const renderTimeline = $(window).width() > 960; - this.set('info', { renderTimeline, showTimeline: renderTimeline && !this.get('composerOpen') }); + let info = this.get('info'); + + if (info.get('topicProgressExpanded')) { + + info.setProperties({ + renderTimeline: true, + renderAdminMenuButton: true + }); + + } else { + + let renderTimeline = !this.site.mobileView; + + if (renderTimeline) { + + const width = $(window).width(); + let height = $(window).height(); + + if (this.get('composerOpen')) { + height -= $('#reply-control').height(); + } + + renderTimeline = width > 960 && height > 520; + } + + info.setProperties({ + renderTimeline, + renderAdminMenuButton: !renderTimeline + }); + } + }, + + // we need to store this so topic progress has something to init with + _topicScrolled(event) { + this.set('info.prevEvent', event); + }, + + @observes('info.topicProgressExpanded') + _expanded() { + if (this.get('info.topicProgressExpanded')) { + $(window).on('click.hide-fullscreen', (e) => { + if (!$(e.target).parents().is('.timeline-container, #topic-progress-wrapper')) { + this._collapseFullscreen(); + } + }); + } else { + $(window).off('click.hide-fullscreen'); + } + this._checkSize(); }, composerOpened() { this.set('composerOpen', true); - this._checkSize(); + // we need to do the check after animation is done + setTimeout(()=>this._checkSize(), 500); }, composerClosed() { @@ -19,25 +67,66 @@ export default Ember.Component.extend({ this._checkSize(); }, + _collapseFullscreen() { + if (this.get('info.topicProgressExpanded')) { + $('.timeline-fullscreen').removeClass('show'); + setTimeout(() => { + this.set('info.topicProgressExpanded', false); + this._checkSize(); + },500); + } + }, + + keyboardTrigger(e) { + if(e.type === "jump") { + bootbox.prompt(I18n.t('topic.progress.jump_prompt_long'), postIndex => { + if (postIndex === null) { return; } + this.sendAction('jumpToIndex', postIndex); + }); + + // this is insanely hacky, for some reason shown event never fires, + // something is bust in bootbox + // TODO upgrade bootbox to see if this hack can be removed + setTimeout(()=>{ + $('.bootbox.modal').trigger('shown'); + },50); + + } + }, + didInsertElement() { this._super(); + this.appEvents + .on('topic:current-post-scrolled', this, this._topicScrolled) + .on('topic:jump-to-post', this, this._collapseFullscreen) + .on('topic:keyboard-trigger', this, this.keyboardTrigger); + if (!this.site.mobileView) { $(window).on('resize.discourse-topic-navigation', () => this._checkSize()); this.appEvents.on('composer:will-open', this, this.composerOpened); this.appEvents.on('composer:will-close', this, this.composerClosed); - this._checkSize(); - } else { - this.set('info', null); + $('#reply-control').on('div-resized.discourse-topic-navigation', () => this._checkSize()); } + + this._checkSize(); }, willDestroyElement() { this._super(); + + this.appEvents + .off('topic:current-post-scrolled', this, this._topicScrolled) + .off('topic:jump-to-post', this, this._collapseFullscreen) + .off('topic:keyboard-trigger', this, this.keyboardTrigger); + + $(window).off('click.hide-fullscreen'); + if (!this.site.mobileView) { $(window).off('resize.discourse-topic-navigation'); this.appEvents.off('composer:will-open', this, this.composerOpened); this.appEvents.off('composer:will-close', this, this.composerClosed); + $('#reply-control').off('div-resized.discourse-topic-navigation'); } } }); diff --git a/app/assets/javascripts/discourse/components/topic-progress.js.es6 b/app/assets/javascripts/discourse/components/topic-progress.js.es6 index 86533ed6a65..768ad10356b 100644 --- a/app/assets/javascripts/discourse/components/topic-progress.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-progress.js.es6 @@ -2,13 +2,11 @@ import { default as computed, observes } from 'ember-addons/ember-computed-decor export default Ember.Component.extend({ elementId: 'topic-progress-wrapper', - classNameBindings: ['docked', 'hidden'], + classNameBindings: ['docked'], expanded: false, - toPostIndex: null, docked: false, progressPosition: null, postStream: Ember.computed.alias('topic.postStream'), - userWantsToJump: null, _streamPercentage: null, init() { @@ -16,26 +14,6 @@ export default Ember.Component.extend({ (this.get('delegated') || []).forEach(m => this.set(m, m)); }, - @computed('userWantsToJump', 'showTimeline') - hidden(userWantsToJump, showTimeline) { - return !userWantsToJump && showTimeline; - }, - - @observes('hidden') - visibilityChanged() { - if (!this.get('hidden')) { - this._updateBar(); - } - }, - - keyboardTrigger(kbdEvent) { - if (kbdEvent.type === 'jump') { - this.set('expanded', true); - this.set('userWantsToJump', true); - Ember.run.scheduleOnce('afterRender', () => this.$('.jump-form input').focus()); - } - }, - @computed('progressPosition') jumpTopDisabled(progressPosition) { return progressPosition <= 3; @@ -83,10 +61,15 @@ export default Ember.Component.extend({ .on("composer:resized", this, this._dock) .on('composer:closed', this, this._dock) .on("topic:scrolled", this, this._dock) - .on('topic:current-post-scrolled', this, this._topicScrolled) - .on('topic-progress:keyboard-trigger', this, this.keyboardTrigger); + .on('topic:current-post-scrolled', this, this._topicScrolled); - Ember.run.scheduleOnce('afterRender', this, this._updateProgressBar); + const prevEvent = this.get('prevEvent'); + if (prevEvent) { + this._topicScrolled(prevEvent); + } else { + Ember.run.scheduleOnce('afterRender', this, this._updateProgressBar); + } + Ember.run.scheduleOnce('afterRender', this, this._dock); }, willDestroyElement() { @@ -95,12 +78,11 @@ export default Ember.Component.extend({ .off("composer:resized", this, this._dock) .off('composer:closed', this, this._dock) .off('topic:scrolled', this, this._dock) - .off('topic:current-post-scrolled', this, this._topicScrolled) - .off('topic-progress:keyboard-trigger'); + .off('topic:current-post-scrolled', this, this._topicScrolled); }, _updateProgressBar() { - if (this.isDestroyed || this.isDestroying || this.get('hidden')) { return; } + if (this.isDestroyed || this.isDestroying) { return; } const $topicProgress = this.$('#topic-progress'); // speeds up stuff, bypass jquery slowness and extra checks @@ -127,6 +109,10 @@ export default Ember.Component.extend({ offset = window.pageYOffset || $('html').scrollTop(), topicProgressHeight = $('#topic-progress').height(); + if (!$topicProgressWrapper || $topicProgressWrapper.length === 0) { + return; + } + let isDocked = false; if (maximumOffset) { const threshold = maximumOffset.top; @@ -156,71 +142,11 @@ export default Ember.Component.extend({ } }, - keyDown(e) { - if (this.get('expanded')) { - if (e.keyCode === 13) { - this.$('input').blur(); - this.send('jumpPost'); - } else if (e.keyCode === 27) { - this.send('toggleExpansion'); - this.set('userWantsToJump', false); - } - } - }, - - _jumpTo(postIndex) { - postIndex = parseInt(postIndex, 10); - - // Validate the post index first - if (isNaN(postIndex) || postIndex < 1) { - postIndex = 1; - } - if (postIndex > this.get('postStream.filteredPostsCount')) { - postIndex = this.get('postStream.filteredPostsCount'); - } - this.set('toPostIndex', postIndex); - this._beforeJump(); - this.sendAction('jumpToIndex', postIndex); - }, actions: { - toggleExpansion(opts) { + toggleExpansion() { this.toggleProperty('expanded'); - if (this.get('expanded')) { - this.set('userWantsToJump', false); - this.set('toPostIndex', this.get('progressPosition')); - if (opts && opts.highlight) { - Ember.run.next(() => $('.jump-form input').select().focus()); - } - if (!this.site.mobileView && !this.capabilities.isIOS) { - Ember.run.schedule('afterRender', () => this.$('input').focus()); - } - } - }, - - jumpPrompt() { - const postIndex = prompt(I18n.t('topic.progress.jump_prompt_long')); - if (postIndex === null) { return; } - this._jumpTo(postIndex); - }, - - jumpPost() { - this._jumpTo(this.get('toPostIndex')); - }, - - jumpTop() { - this._beforeJump(); - this.sendAction('jumpTop'); - }, - - jumpBottom() { - this._beforeJump(); - this.sendAction('jumpBottom'); } }, - _beforeJump() { - this.set('expanded', false); - this.set('userWantsToJump', false); - } }); diff --git a/app/assets/javascripts/discourse/components/topic-timeline.js.es6 b/app/assets/javascripts/discourse/components/topic-timeline.js.es6 index ee77495b2ae..eaa065857cb 100644 --- a/app/assets/javascripts/discourse/components/topic-timeline.js.es6 +++ b/app/assets/javascripts/discourse/components/topic-timeline.js.es6 @@ -10,12 +10,28 @@ export default MountWidget.extend(Docking, { dockAt: null, buildArgs() { - return { topic: this.get('topic'), - topicTrackingState: this.topicTrackingState, - enteredIndex: this.get('enteredIndex'), - dockAt: this.dockAt, - top: this.dockAt || FIXED_POS, - dockBottom: this.dockBottom }; + let attrs = { + topic: this.get('topic'), + topicTrackingState: this.topicTrackingState, + enteredIndex: this.get('enteredIndex'), + dockBottom: this.dockBottom, + mobileView: this.get('site.mobileView') + }; + + let event = this.get('prevEvent'); + if (event) { + attrs.enteredIndex = event.postIndex-1; + } + + if (this.get('fullscreen')) { + attrs.fullScreen = true; + attrs.addShowClass = this.get('addShowClass'); + } else { + attrs.dockAt = this.dockAt; + attrs.top = this.dockAt || FIXED_POS; + } + + return attrs; }, @observes('topic.highest_post_number', 'loading') @@ -54,6 +70,14 @@ export default MountWidget.extend(Docking, { didInsertElement() { this._super(); + + if (this.get('fullscreen') && !this.get('addShowClass')) { + Em.run.next(()=>{ + this.set('addShowClass', true); + this.queueRerender(); + }); + } + this.dispatch('topic:current-post-scrolled', 'timeline-scrollarea'); this.dispatch('topic-notifications-button:keyboard-trigger', 'topic-notifications-button'); } diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 067f6510814..cf634b98325 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -670,6 +670,8 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { return; } + this.appEvents.trigger('topic:jump-to-post', postId); + const topic = this.get('model'); const postStream = topic.get('postStream'); const post = postStream.findLoadedPost(postId); diff --git a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 index 49e543a52ee..477b914ce58 100644 --- a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 +++ b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 @@ -4,7 +4,7 @@ import { scrollTopFor } from 'discourse/lib/offset-calculator'; const bindings = { '!': {postAction: 'showFlags'}, - '#': {handler: 'toggleProgress', anonymous: true}, + '#': {handler: 'goToPost', anonymous: true}, '/': {handler: 'toggleSearch', anonymous: true}, '=': {handler: 'toggleHamburgerMenu', anonymous: true}, '?': {handler: 'showHelpModal', anonymous: true}, @@ -170,8 +170,8 @@ export default { this.container.lookup('controller:topic').togglePinnedState(); }, - toggleProgress() { - this.appEvents.trigger('topic-progress:keyboard-trigger', { type: 'jump' }); + goToPost() { + this.appEvents.trigger('topic:keyboard-trigger', { type: 'jump' }); }, toggleSearch(event) { diff --git a/app/assets/javascripts/discourse/templates/components/topic-progress.hbs b/app/assets/javascripts/discourse/templates/components/topic-progress.hbs index a840c135645..8c91ce73d7d 100644 --- a/app/assets/javascripts/discourse/templates/components/topic-progress.hbs +++ b/app/assets/javascripts/discourse/templates/components/topic-progress.hbs @@ -1,33 +1,8 @@ -{{#unless hidden}} - {{#if expanded}} - - {{/if}} - -{{/unless}} + diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index 638eceb6b0a..f6b97c0fc82 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -66,17 +66,26 @@
{{partial "selected-posts"}}
- {{#topic-navigation as |info|}} - {{#if info.renderTimeline}} - {{topic-timeline topic=model - enteredIndex=enteredIndex - loading=model.postStream.loading - delegated=topicDelegated}} - {{else}} + + {{#topic-navigation jumpToIndex="jumpToIndex" as |info|}} + + {{#if info.renderAdminMenuButton}} {{topic-admin-menu-button topic=model fixed="true" delegated=topicDelegated}} {{/if}} - {{topic-progress topic=model delegated=topicDelegated showTimeline=info.showTimeline}} + {{#if info.renderTimeline}} + {{topic-timeline topic=model + prevEvent=info.prevEvent + fullscreen=info.topicProgressExpanded + enteredIndex=enteredIndex + loading=model.postStream.loading + delegated=topicDelegated}} + + {{else}} + {{topic-progress prevEvent=info.prevEvent topic=model delegated=topicDelegated expanded=info.topicProgressExpanded}} + {{/if}} + + {{/topic-navigation}}
diff --git a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 index 1056fb282f2..a9ed81102b4 100644 --- a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 +++ b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 @@ -2,6 +2,7 @@ import { createWidget } from 'discourse/widgets/widget'; import { h } from 'virtual-dom'; import { relativeAge } from 'discourse/lib/formatter'; import { iconNode } from 'discourse/helpers/fa-icon-node'; +import RawHtml from 'discourse/widgets/raw-html'; const SCROLLAREA_HEIGHT = 300; const SCROLLER_HEIGHT = 50; @@ -182,6 +183,13 @@ createWidget('timeline-scrollarea', { createWidget('topic-timeline-container', { tagName: 'div.timeline-container', buildClasses(attrs) { + if (attrs.fullScreen) { + if (attrs.addShowClass) { + return 'timeline-fullscreen show'; + } else { + return 'timeline-fullscreen'; + } + } if (attrs.dockAt) { const result = ['timeline-docked']; if (attrs.dockBottom) { @@ -192,7 +200,9 @@ createWidget('topic-timeline-container', { }, buildAttributes(attrs) { - return { style: `top: ${attrs.top}px` }; + if (attrs.top) { + return { style: `top: ${attrs.top}px` }; + } }, html(attrs) { @@ -209,9 +219,15 @@ export default createWidget('topic-timeline', { const stream = attrs.topic.get('postStream.stream'); const { currentUser } = this; - let result = []; - if (currentUser && currentUser.get('canManageTopic')) { + + if (attrs.mobileView) { + const titleHTML = new RawHtml({ html: `${topic.get('fancyTitle')}` }); + result.push(h('h3.title', titleHTML)); + } + + + if (!attrs.fullScreen && currentUser && currentUser.get('canManageTopic')) { result.push(h('div.timeline-controls', this.attach('topic-admin-menu-button', { topic }))); } @@ -234,7 +250,7 @@ export default createWidget('topic-timeline', { if (currentUser) { const controls = []; - if (attrs.topic.get('details.can_create_post')) { + if (!attrs.fullScreen && attrs.topic.get('details.can_create_post')) { controls.push(this.attach('button', { className: 'btn create', icon: 'reply', @@ -243,7 +259,7 @@ export default createWidget('topic-timeline', { })); } - if (currentUser) { + if (currentUser && !attrs.fullScreen) { controls.push(this.attach('topic-notifications-button', { topic })); } result.push(h('div.timeline-footer-controls', controls)); diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 49b4ad95700..abb6e9bfa80 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -12,3 +12,4 @@ @import "common/printer-friendly"; @import "common/base/*"; @import "common/d-editor"; +@import "common/topic-timeline"; diff --git a/app/assets/stylesheets/desktop/topic-timeline.scss b/app/assets/stylesheets/common/topic-timeline.scss similarity index 83% rename from app/assets/stylesheets/desktop/topic-timeline.scss rename to app/assets/stylesheets/common/topic-timeline.scss index e44841a847e..8e606dbb90b 100644 --- a/app/assets/stylesheets/desktop/topic-timeline.scss +++ b/app/assets/stylesheets/common/topic-timeline.scss @@ -2,14 +2,6 @@ width: 900px; } -.composer-open { - .topic-timeline { - opacity: 0; - pointer-events: none; - cursor: default; - } -} - .timeline-container { box-sizing: border-box; z-index: 499; @@ -37,6 +29,31 @@ } } + &.timeline-fullscreen.show { + max-height: 700px; + transition: max-height 0.4s ease-out; + } + + &.timeline-fullscreen { + max-height: 0; + transition: max-height 0.3s ease-in; + position: fixed; + margin-left: 0; + background-color: $secondary; + bottom: 0; + left: 0; + right: 0; + border-top: 1px solid dark-light-choose(scale-color($primary, $lightness: 90%), scale-color($secondary, $lightness: 90%)); + padding-top: 15px; + z-index: 100000; + .topic-timeline { + width: auto; + margin-left: 1.5em; + margin-right: 1.5em; + } + + } + .topic-timeline { margin-left: 3em; width: 150px; diff --git a/app/assets/stylesheets/desktop.scss b/app/assets/stylesheets/desktop.scss index 5687b1c47bd..989d6cb2864 100644 --- a/app/assets/stylesheets/desktop.scss +++ b/app/assets/stylesheets/desktop.scss @@ -13,7 +13,6 @@ @import "desktop/category-list"; @import "desktop/topic-list"; @import "desktop/topic-post"; -@import "desktop/topic-timeline"; @import "desktop/topic"; @import "desktop/upload"; @import "desktop/user"; diff --git a/vendor/assets/javascripts/div_resizer.js b/vendor/assets/javascripts/div_resizer.js index 07a99144f87..66ddfe9585b 100644 --- a/vendor/assets/javascripts/div_resizer.js +++ b/vendor/assets/javascripts/div_resizer.js @@ -67,6 +67,7 @@ endDrag = function(e, opts) { if (typeof opts.resize === "function") { opts.resize(); } + $(div).trigger("div-resized"); div = null; };