From 751e354ca62a5bea2ec9136b3091c26cc4bf2995 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 16 May 2016 15:26:04 -0400 Subject: [PATCH] Refactor `topic-progress` into a component --- .../components/topic-progress.js.es6 | 205 ++++++++++++++++++ .../controllers/topic-progress.js.es6 | 111 ---------- .../discourse/controllers/topic.js.es6 | 16 +- .../discourse/lib/keyboard-shortcuts.js.es6 | 2 +- .../javascripts/discourse/lib/url.js.es6 | 5 - .../discourse/routes/topic-from-params.js.es6 | 11 +- .../javascripts/discourse/routes/topic.js.es6 | 2 - .../{ => components}/topic-progress.hbs | 4 +- .../javascripts/discourse/templates/topic.hbs | 5 +- .../discourse/views/topic-progress.js.es6 | 109 ---------- .../javascripts/controllers/topic-test.js.es6 | 2 +- 11 files changed, 229 insertions(+), 243 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/topic-progress.js.es6 delete mode 100644 app/assets/javascripts/discourse/controllers/topic-progress.js.es6 rename app/assets/javascripts/discourse/templates/{ => components}/topic-progress.hbs (90%) delete mode 100644 app/assets/javascripts/discourse/views/topic-progress.js.es6 diff --git a/app/assets/javascripts/discourse/components/topic-progress.js.es6 b/app/assets/javascripts/discourse/components/topic-progress.js.es6 new file mode 100644 index 00000000000..e87f66ebbd9 --- /dev/null +++ b/app/assets/javascripts/discourse/components/topic-progress.js.es6 @@ -0,0 +1,205 @@ +import DiscourseURL from 'discourse/lib/url'; +import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; + +export default Ember.Component.extend({ + elementId: 'topic-progress-wrapper', + classNameBindings: ['docked'], + expanded: false, + toPostIndex: null, + docked: false, + + postStream: Ember.computed.alias('topic.postStream'), + + @computed('postStream.loaded', 'progressPosition', 'postStream.filteredPostsCount', 'postStream.highest_post_number') + streamPercentage(loaded, progressPosition, filteredPostsCount, highestPostNumber) { + if (!loaded) { return 0; } + if (highestPostNumber === 0) { return 0; } + const perc = progressPosition / filteredPostsCount; + return (perc > 1.0) ? 1.0 : perc; + }, + + @computed('progressPosition') + jumpTopDisabled(progressPosition) { + return progressPosition <= 3; + }, + + @computed('postStream.filteredPostsCount', 'topic.highest_post_number', 'progressPosition') + jumpBottomDisabled(filteredPostsCount, highestPostNumber, progressPosition) { + return progressPosition >= filteredPostsCount || progressPosition >= highestPostNumber; + }, + + + @computed('postStream.loaded', 'topic.currentPost', 'postStream.filteredPostsCount') + hideProgress(loaded, currentPost, filteredPostsCount) { + return (!loaded) || (!currentPost) || (filteredPostsCount < 2); + }, + + @computed('postStream.filteredPostsCount') + hugeNumberOfPosts(filteredPostsCount) { + return filteredPostsCount >= this.siteSettings.short_progress_text_threshold; + }, + + @computed('hugeNumberOfPosts', 'topic.highest_post_number') + jumpToBottomTitle(hugeNumberOfPosts, highestPostNumber) { + if (hugeNumberOfPosts) { + return I18n.t('topic.progress.jump_bottom_with_number', { post_number: highestPostNumber }); + } else { + return I18n.t('topic.progress.jump_bottom'); + } + }, + + @observes('streamPercentage', 'postStream.stream.[]') + _updateBar() { + Ember.run.scheduleOnce('afterRender', this, this._updateProgressBar); + }, + + didInsertElement() { + this._super(); + this.appEvents.on("composer:opened", this, this._dock) + .on("composer:resized", this, this._dock) + .on("composer:closed", this, this._dock) + .on("topic:scrolled", this, this._dock); + + // Reflows are expensive. Cache the jQuery selector + // and the width when inserted into the DOM + this._$topicProgress = this.$('#topic-progress'); + + Ember.run.scheduleOnce('afterRender', this, this._updateProgressBar); + }, + + willDestroyElement() { + this._super(); + this.appEvents.off("composer:opened", this, this._dock) + .off("composer:resized", this, this._dock) + .off("composer:closed", this, this._dock) + .off('topic:scrolled', this, this._dock); + }, + + _updateProgressBar() { + // speeds up stuff, bypass jquery slowness and extra checks + if (!this._totalWidth) { + this._totalWidth = this._$topicProgress[0].offsetWidth; + } + const totalWidth = this._totalWidth; + const progressWidth = this.get('streamPercentage') * totalWidth; + + this._$topicProgress.find('.bg') + .css("border-right-width", (progressWidth === totalWidth) ? "0px" : "1px") + .width(progressWidth); + }, + + _dock() { + const maximumOffset = $('#topic-footer-buttons').offset(), + composerHeight = $('#reply-control').height() || 0, + $topicProgressWrapper = this.$(), + style = $topicProgressWrapper.attr('style') || '', + offset = window.pageYOffset || $('html').scrollTop(); + + let isDocked = false; + if (maximumOffset) { + const threshold = maximumOffset.top, + windowHeight = $(window).height(), + topicProgressHeight = $('#topic-progress').height(); + + isDocked = offset >= threshold - windowHeight + topicProgressHeight + composerHeight; + } + + if (composerHeight > 0) { + if (isDocked) { + if (style.indexOf('bottom') >= 0) { + $topicProgressWrapper.css('bottom', ''); + } + } else { + const height = composerHeight + "px"; + if ($topicProgressWrapper.css('bottom') !== height) { + $topicProgressWrapper.css('bottom', height); + } + } + } else { + if (style.indexOf('bottom') >= 0) { + $topicProgressWrapper.css('bottom', ''); + } + } + this.set('docked', isDocked); + }, + + click(e) { + if ($(e.target).parents('#topic-progress').length) { + this.send('toggleExpansion'); + } + }, + + keyDown(e) { + if (this.get('expanded')) { + if (e.keyCode === 13) { + this.$('input').blur(); + this.send('jumpPost'); + } else if (e.keyCode === 27) { + this.send('toggleExpansion'); + } + } + }, + + jumpTo(url) { + this.set('expanded', false); + DiscourseURL.routeTo(url); + }, + + actions: { + toggleExpansion(opts) { + this.toggleProperty('expanded'); + if (this.get('expanded')) { + this.set('toPostIndex', this.get('progressPosition')); + if(opts && opts.highlight){ + // TODO: somehow move to view? + Em.run.next(function(){ + $('.jump-form input').select().focus(); + }); + } + if (!this.site.mobileView && !this.capabilities.isIOS) { + Ember.run.schedule('afterRender', () => this.$('input').focus()); + } + } + }, + + jumpPost() { + let postIndex = parseInt(this.get('toPostIndex'), 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); + const stream = this.get('postStream'); + const postId = stream.findPostIdForPostNumber(postIndex); + + if (!postId) { + Em.Logger.warn("jump-post code broken - requested an index outside the stream array"); + return; + } + + const post = stream.findLoadedPost(postId); + if (post) { + this.jumpTo(this.get('topic').urlForPostNumber(post.get('post_number'))); + } else { + // need to load it + stream.findPostsByIds([postId]).then(arr => { + this.jumpTo(this.get('topic').urlForPostNumber(arr[0].get('post_number'))); + }); + } + }, + + jumpTop() { + this.set('expanded', false); + this.sendAction('jumpTop'); + }, + + jumpBottom() { + this.set('expanded', false); + this.sendAction('jumpBottom'); + } + } +}); diff --git a/app/assets/javascripts/discourse/controllers/topic-progress.js.es6 b/app/assets/javascripts/discourse/controllers/topic-progress.js.es6 deleted file mode 100644 index 324d3b7357b..00000000000 --- a/app/assets/javascripts/discourse/controllers/topic-progress.js.es6 +++ /dev/null @@ -1,111 +0,0 @@ -import DiscourseURL from 'discourse/lib/url'; - -export default Ember.Controller.extend({ - needs: ['topic'], - progressPosition: null, - expanded: false, - toPostIndex: null, - - actions: { - toggleExpansion(opts) { - this.toggleProperty('expanded'); - if (this.get('expanded')) { - this.set('toPostIndex', this.get('progressPosition')); - if(opts && opts.highlight){ - // TODO: somehow move to view? - Em.run.next(function(){ - $('.jump-form input').select().focus(); - }); - } - } - }, - - jumpPost() { - var postIndex = parseInt(this.get('toPostIndex'), 10); - - // Validate the post index first - if (isNaN(postIndex) || postIndex < 1) { - postIndex = 1; - } - if (postIndex > this.get('model.postStream.filteredPostsCount')) { - postIndex = this.get('model.postStream.filteredPostsCount'); - } - this.set('toPostIndex', postIndex); - var stream = this.get('model.postStream'), - postId = stream.findPostIdForPostNumber(postIndex); - - if (!postId) { - Em.Logger.warn("jump-post code broken - requested an index outside the stream array"); - return; - } - - var post = stream.findLoadedPost(postId); - if (post) { - this.jumpTo(this.get('model').urlForPostNumber(post.get('post_number'))); - } else { - var self = this; - // need to load it - stream.findPostsByIds([postId]).then(function(arr) { - post = arr[0]; - self.jumpTo(self.get('model').urlForPostNumber(post.get('post_number'))); - }); - } - }, - - jumpTop() { - this.jumpTo(this.get('model.firstPostUrl')); - }, - - jumpBottom() { - this.jumpTo(this.get('model.lastPostUrl')); - } - }, - - // Route and close the expansion - jumpTo(url) { - this.set('expanded', false); - DiscourseURL.routeTo(url); - }, - - streamPercentage: function() { - if (!this.get('model.postStream.loaded')) { return 0; } - if (this.get('model.postStream.highest_post_number') === 0) { return 0; } - var perc = this.get('progressPosition') / this.get('model.postStream.filteredPostsCount'); - return (perc > 1.0) ? 1.0 : perc; - }.property('model.postStream.loaded', 'progressPosition', 'model.postStream.filteredPostsCount'), - - jumpTopDisabled: function() { - return this.get('progressPosition') <= 3; - }.property('progressPosition'), - - filteredPostCountChanged: function(){ - if(this.get('model.postStream.filteredPostsCount') < this.get('progressPosition')){ - this.set('progressPosition', this.get('model.postStream.filteredPostsCount')); - } - }.observes('model.postStream.filteredPostsCount'), - - jumpBottomDisabled: function() { - return this.get('progressPosition') >= this.get('model.postStream.filteredPostsCount') || - this.get('progressPosition') >= this.get('model.highest_post_number'); - }.property('model.postStream.filteredPostsCount', 'model.highest_post_number', 'progressPosition'), - - hideProgress: function() { - if (!this.get('model.postStream.loaded')) return true; - if (!this.get('model.currentPost')) return true; - if (this.get('model.postStream.filteredPostsCount') < 2) return true; - return false; - }.property('model.postStream.loaded', 'model.currentPost', 'model.postStream.filteredPostsCount'), - - hugeNumberOfPosts: function() { - return (this.get('model.postStream.filteredPostsCount') >= Discourse.SiteSettings.short_progress_text_threshold); - }.property('model.highest_post_number'), - - jumpToBottomTitle: function() { - if (this.get('hugeNumberOfPosts')) { - return I18n.t('topic.progress.jump_bottom_with_number', {post_number: this.get('model.highest_post_number')}); - } else { - return I18n.t('topic.progress.jump_bottom'); - } - }.property('hugeNumberOfPosts', 'model.highest_post_number') - -}); diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 7c7c332666d..8ddd92d86ff 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -10,7 +10,7 @@ import DiscourseURL from 'discourse/lib/url'; import { categoryBadgeHTML } from 'discourse/helpers/category-link'; export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { - needs: ['modal', 'composer', 'quote-button', 'topic-progress', 'application'], + needs: ['modal', 'composer', 'quote-button', 'application'], multiSelect: false, allPostsSelected: false, editingTopic: false, @@ -21,10 +21,16 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { enteredAt: null, retrying: false, adminMenuVisible: false, + screenProgressPosition: null, showRecover: Em.computed.and('model.deleted', 'model.details.can_recover'), isFeatured: Em.computed.or("model.pinned_at", "model.isBanner"), + @computed('screenProgressPosition', 'model.postStream.filteredPostsCount') + progressPosition(pp, filteredPostsCount) { + return (filteredPostsCount < pp) ? filteredPostsCount : pp; + }, + _titleChanged: function() { const title = this.get('model.title'); if (!Ember.isEmpty(title)) { @@ -203,7 +209,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { const postStream = this.get('model.postStream'); const lastLoadedPost = postStream.get('posts.lastObject'); - this.set('controllers.topic-progress.progressPosition', postStream.progressIndexOfPost(post)); + this.set('screenProgressPosition', postStream.progressIndexOfPost(post)); if (lastLoadedPost && lastLoadedPost === post && postStream.get('canAppendMore')) { postStream.appendMore().then(() => refresh()); @@ -379,7 +385,11 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { }, jumpTop() { - this.get('controllers.topic-progress').send('jumpTop'); + DiscourseURL.routeTo(this.get('model.firstPostUrl')); + }, + + jumpBottom() { + DiscourseURL.routeTo(this.get('model.lastPostUrl')); }, selectAll() { diff --git a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 index e186bac8045..b62f0885726 100644 --- a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 +++ b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 @@ -116,7 +116,7 @@ export default { _jumpTo(direction) { if ($('.container.posts').length) { - this.container.lookup('controller:topic-progress').send(direction); + this.container.lookup('controller:topic').send(direction); } }, diff --git a/app/assets/javascripts/discourse/lib/url.js.es6 b/app/assets/javascripts/discourse/lib/url.js.es6 index 8fbd12d7d95..a58011f0f09 100644 --- a/app/assets/javascripts/discourse/lib/url.js.es6 +++ b/app/assets/javascripts/discourse/lib/url.js.es6 @@ -232,11 +232,6 @@ const DiscourseURL = Ember.Object.extend({ enteredAt: new Date().getTime().toString() }); - const closestPost = postStream.closestPostForPostNumber(closest); - const progress = postStream.progressIndexOfPost(closestPost); - const progressController = container.lookup('controller:topic-progress'); - - progressController.set('progressPosition', progress); this.appEvents.trigger('post:highlight', closest); }).then(() => { DiscourseURL.jumpToPost(closest, {skipIfOnScreen: true}); diff --git a/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 b/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 index f6d7216f60c..afd819a49e2 100644 --- a/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic-from-params.js.es6 @@ -15,7 +15,6 @@ export default Discourse.Route.extend({ topic = this.modelFor('topic'), postStream = topic.get('postStream'), topicController = this.controllerFor('topic'), - topicProgressController = this.controllerFor('topic-progress'), composerController = this.controllerFor('composer'); // I sincerely hope no topic gets this many posts @@ -28,20 +27,14 @@ export default Discourse.Route.extend({ // we need better handling and logging for this condition. // The post we requested might not exist. Let's find the closest post - const closestPost = postStream.closestPostForPostNumber(params.nearPost || 1), - closest = closestPost.get('post_number'), - progress = postStream.progressIndexOfPost(closestPost); + const closestPost = postStream.closestPostForPostNumber(params.nearPost || 1); + const closest = closestPost.get('post_number'); topicController.setProperties({ 'model.currentPost': closest, enteredAt: new Date().getTime().toString(), }); - topicProgressController.setProperties({ - progressPosition: progress, - expanded: false - }); - // Highlight our post after the next render Ember.run.scheduleOnce('afterRender', function() { self.appEvents.trigger('post:highlight', closest); diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6 index cd627ce46d9..c8d4c4594e9 100644 --- a/app/assets/javascripts/discourse/routes/topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic.js.es6 @@ -210,8 +210,6 @@ const TopicRoute = Discourse.Route.extend({ this.topicTrackingState.trackIncoming('all'); controller.subscribe(); - this.controllerFor('topic-progress').set('model', model); - // We reset screen tracking every time a topic is entered this.screenTrack.start(model.get('id'), controller); } diff --git a/app/assets/javascripts/discourse/templates/topic-progress.hbs b/app/assets/javascripts/discourse/templates/components/topic-progress.hbs similarity index 90% rename from app/assets/javascripts/discourse/templates/topic-progress.hbs rename to app/assets/javascripts/discourse/templates/components/topic-progress.hbs index 682cb2f46f5..602bd78f43e 100644 --- a/app/assets/javascripts/discourse/templates/topic-progress.hbs +++ b/app/assets/javascripts/discourse/templates/components/topic-progress.hbs @@ -18,7 +18,9 @@ {{/if}}