diff --git a/app/assets/javascripts/discourse/controllers/topic-progress.js.es6 b/app/assets/javascripts/discourse/controllers/topic-progress.js.es6 new file mode 100644 index 0000000000..d24ee08c31 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/topic-progress.js.es6 @@ -0,0 +1,46 @@ +export default Ember.ObjectController.extend({ + needs: ['topic'], + progressPosition: null, + + streamPercentage: function() { + if (!this.get('postStream.loaded')) { return 0; } + if (this.get('postStream.highest_post_number') === 0) { return 0; } + var perc = this.get('progressPosition') / this.get('postStream.filteredPostsCount'); + return (perc > 1.0) ? 1.0 : perc; + }.property('postStream.loaded', 'progressPosition', 'postStream.filteredPostsCount'), + + jumpTopDisabled: function() { + return (this.get('progressPosition') < 2); + }.property('progressPosition'), + + filteredPostCountChanged: function(){ + if(this.get('postStream.filteredPostsCount') < this.get('progressPosition')){ + this.set('progressPosition', this.get('postStream.filteredPostsCount')); + } + }.observes('postStream.filteredPostsCount'), + + jumpBottomDisabled: function() { + return this.get('progressPosition') >= this.get('postStream.filteredPostsCount') || + this.get('progressPosition') >= this.get('highest_post_number'); + }.property('postStream.filteredPostsCount', 'highest_post_number', 'progressPosition'), + + hideProgress: function() { + if (!this.get('postStream.loaded')) return true; + if (!this.get('currentPost')) return true; + if (this.get('postStream.filteredPostsCount') < 2) return true; + return false; + }.property('postStream.loaded', 'currentPost', 'postStream.filteredPostsCount'), + + hugeNumberOfPosts: function() { + return (this.get('postStream.filteredPostsCount') >= Discourse.SiteSettings.short_progress_text_threshold); + }.property('highest_post_number'), + + jumpToBottomTitle: function() { + if (this.get('hugeNumberOfPosts')) { + return I18n.t('topic.progress.jump_bottom_with_number', {post_number: this.get('highest_post_number')}); + } else { + return I18n.t('topic.progress.jump_bottom'); + } + }.property('hugeNumberOfPosts', 'highest_post_number') + +}); diff --git a/app/assets/javascripts/discourse/controllers/topic_controller.js b/app/assets/javascripts/discourse/controllers/topic_controller.js index 1ea8ffabc3..bc8dd7b81e 100644 --- a/app/assets/javascripts/discourse/controllers/topic_controller.js +++ b/app/assets/javascripts/discourse/controllers/topic_controller.js @@ -8,7 +8,7 @@ **/ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.SelectedPostsCount, { multiSelect: false, - needs: ['header', 'modal', 'composer', 'quote-button', 'search'], + needs: ['header', 'modal', 'composer', 'quote-button', 'search', 'topic-progress'], allPostsSelected: false, editingTopic: false, selectedPosts: null, @@ -391,21 +391,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected return post.get('post_number') === 1 && post.get('topic.expandable_first_post'); }.property(), - jumpTopDisabled: function() { - return (this.get('progressPosition') < 2); - }.property('progressPosition'), - - filteredPostCountChanged: function(){ - if(this.get('postStream.filteredPostsCount') < this.get('progressPosition')){ - this.set('progressPosition', this.get('postStream.filteredPostsCount')); - } - }.observes('postStream.filteredPostsCount'), - - jumpBottomDisabled: function() { - return this.get('progressPosition') >= this.get('postStream.filteredPostsCount') || - this.get('progressPosition') >= this.get('highest_post_number'); - }.property('postStream.filteredPostsCount', 'highest_post_number', 'progressPosition'), - canMergeTopic: function() { if (!this.get('details.can_move_posts')) return false; return (this.get('selectedPostsCount') > 0); @@ -451,13 +436,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected hasError: Ember.computed.or('errorBodyHtml', 'message'), - streamPercentage: function() { - if (!this.get('postStream.loaded')) { return 0; } - if (this.get('postStream.highest_post_number') === 0) { return 0; } - var perc = this.get('progressPosition') / this.get('postStream.filteredPostsCount'); - return (perc > 1.0) ? 1.0 : perc; - }.property('postStream.loaded', 'progressPosition', 'postStream.filteredPostsCount'), - multiSelectChanged: function() { // Deselect all posts when multi select is turned off if (!this.get('multiSelect')) { @@ -465,25 +443,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected } }.observes('multiSelect'), - hideProgress: function() { - if (!this.get('postStream.loaded')) return true; - if (!this.get('currentPost')) return true; - if (this.get('postStream.filteredPostsCount') < 2) return true; - return false; - }.property('postStream.loaded', 'currentPost', 'postStream.filteredPostsCount'), - - hugeNumberOfPosts: function() { - return (this.get('postStream.filteredPostsCount') >= Discourse.SiteSettings.short_progress_text_threshold); - }.property('highest_post_number'), - - jumpToBottomTitle: function() { - if (this.get('hugeNumberOfPosts')) { - return I18n.t('topic.progress.jump_bottom_with_number', {post_number: this.get('highest_post_number')}); - } else { - return I18n.t('topic.progress.jump_bottom'); - } - }.property('hugeNumberOfPosts', 'highest_post_number'), - deselectPost: function(post) { this.get('selectedPosts').removeObject(post); @@ -663,7 +622,7 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected lastLoadedPost = postStream.get('lastLoadedPost'), index = postStream.get('stream').indexOf(post.get('id'))+1; - this.set('progressPosition', index); + this.set('controllers.topic-progress.progressPosition', index); if (lastLoadedPost && lastLoadedPost === post) { postStream.appendMore(); diff --git a/app/assets/javascripts/discourse/lib/url.js b/app/assets/javascripts/discourse/lib/url.js index 6134da1912..851b0a64e3 100644 --- a/app/assets/javascripts/discourse/lib/url.js +++ b/app/assets/javascripts/discourse/lib/url.js @@ -162,7 +162,9 @@ Discourse.URL = Em.Object.createWithMixins({ if (oldTopicId === newTopicId) { Discourse.URL.replaceState(path); - var topicController = Discourse.__container__.lookup('controller:topic'), + var container = Discourse.__container__, + topicController = container.lookup('controller:topic'), + topicProgressController = container.lookup('controller:topic-progress'), opts = {}, postStream = topicController.get('postStream'); @@ -173,10 +175,10 @@ Discourse.URL = Em.Object.createWithMixins({ postStream.refresh(opts).then(function() { topicController.setProperties({ currentPost: closest, - progressPosition: closest, highlightOnInsert: closest, enteredAt: new Date().getTime().toString() }); + topicProgressController.set('progressPosition', closest); }).then(function() { Discourse.TopicView.jumpToPost(closest); }); diff --git a/app/assets/javascripts/discourse/routes/topic_from_params_route.js b/app/assets/javascripts/discourse/routes/topic_from_params_route.js index ded638a392..667819a07f 100644 --- a/app/assets/javascripts/discourse/routes/topic_from_params_route.js +++ b/app/assets/javascripts/discourse/routes/topic_from_params_route.js @@ -15,6 +15,7 @@ Discourse.TopicFromParamsRoute = Discourse.Route.extend({ postStream = topic.get('postStream'); var topicController = this.controllerFor('topic'), + topicProgressController = this.controllerFor('topic-progress'), composerController = this.controllerFor('composer'); // I sincerely hope no topic gets this many posts @@ -26,11 +27,11 @@ Discourse.TopicFromParamsRoute = Discourse.Route.extend({ topicController.setProperties({ currentPost: closest, - progressPosition: closest, enteredAt: new Date().getTime().toString(), highlightOnInsert: closest }); + topicProgressController.set('progressPosition', closest); Discourse.TopicView.jumpToPost(closest); if (topic.present('draft')) { diff --git a/app/assets/javascripts/discourse/routes/topic_route.js b/app/assets/javascripts/discourse/routes/topic_route.js index 641269296b..0d0a097e74 100644 --- a/app/assets/javascripts/discourse/routes/topic_route.js +++ b/app/assets/javascripts/discourse/routes/topic_route.js @@ -182,6 +182,7 @@ Discourse.TopicRoute = Discourse.Route.extend({ Discourse.TopicTrackingState.current().trackIncoming('all'); controller.subscribe(); + this.controllerFor('topic-progress').set('model', model); // We reset screen tracking every time a topic is entered Discourse.ScreenTrack.current().start(model.get('id'), controller); } diff --git a/app/assets/javascripts/discourse/templates/topic-progress.js.handlebars b/app/assets/javascripts/discourse/templates/topic-progress.js.handlebars new file mode 100644 index 0000000000..958db65e63 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/topic-progress.js.handlebars @@ -0,0 +1,8 @@ + diff --git a/app/assets/javascripts/discourse/templates/topic.js.handlebars b/app/assets/javascripts/discourse/templates/topic.js.handlebars index b3d3483919..8a7627a2d3 100644 --- a/app/assets/javascripts/discourse/templates/topic.js.handlebars +++ b/app/assets/javascripts/discourse/templates/topic.js.handlebars @@ -62,16 +62,7 @@
-
- -
+ {{render 'topic-progress'}} {{#if postStream.loadingAbove}}
{{i18n loading}}
diff --git a/app/assets/javascripts/discourse/views/topic-progress.js.es6 b/app/assets/javascripts/discourse/views/topic-progress.js.es6 new file mode 100644 index 0000000000..de65608b1b --- /dev/null +++ b/app/assets/javascripts/discourse/views/topic-progress.js.es6 @@ -0,0 +1,76 @@ +export default Ember.View.extend({ + elementId: 'topic-progress-wrapper', + + _inserted: function() { + // This get seems counter intuitive, but it's to trigger the observer on + // the streamPercentage for this view. Otherwise the process bar does not + // update. + this.get('controller.streamPercentage'); + + this.appEvents.on("composer:opened", this, '_dock') + .on("composer:resized", this, '_dock') + .on("composer:closed", this, '_dock') + .on("topic:scrolled", this, '_dock'); + + // Reflows are expensive. Cache the jQuery selector + // and the width when inserted into the DOM + this._$topicProgress = this.$('#topic-progress'); + this._progressWidth = this._$topicProgress[0].offsetWidth; + }.on('didInsertElement'), + + _unbindEvents: function() { + this.appEvents.off("composer:opened", this, '_dock') + .off("composer:resized", this, '_dock') + .off("composer:closed", this, '_dock') + .off('topic:scrolled', this, '_dock'); + }.on('willDestroyElement'), + + _updateBar: function() { + Em.run.scheduleOnce('afterRender', this, '_updateProgressBar'); + }.observes('controller.streamPercentage', 'postStream.stream.@each'), + + _updateProgressBar: function() { + // speeds up stuff, bypass jquery slowness and extra checks + var totalWidth = this._progressWidth, + progressWidth = this.get('controller.streamPercentage') * totalWidth; + + this._$topicProgress.find('.bg') + .css("border-right-width", (progressWidth === totalWidth) ? "0px" : "1px") + .width(progressWidth); + }, + + _dock: function () { + var maximumOffset = $('#topic-footer-buttons').offset(), + composerHeight = $('#reply-control').height() || 0, + $topicProgressWrapper = this.$(), + style = $topicProgressWrapper.attr('style') || '', + isDocked = false, + offset = window.pageYOffset || $('html').scrollTop(); + + if (maximumOffset) { + var 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 { + var height = composerHeight + "px"; + if ($topicProgressWrapper.css('bottom') !== height) { + $topicProgressWrapper.css('bottom', height); + } + } + } else { + if (style.indexOf('bottom') >= 0) { + $topicProgressWrapper.css('bottom', ''); + } + } + } + +}); diff --git a/app/assets/javascripts/discourse/views/topic_view.js b/app/assets/javascripts/discourse/views/topic_view.js index 8d43845334..536a07552b 100644 --- a/app/assets/javascripts/discourse/views/topic_view.js +++ b/app/assets/javascripts/discourse/views/topic_view.js @@ -23,35 +23,6 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { postStream: Em.computed.alias('controller.postStream'), - updateBar: function() { - Em.run.scheduleOnce('afterRender', this, '_updateProgressBar'); - }.observes('controller.streamPercentage', 'postStream.stream.@each'), - - _updateProgressBar: function() { - var $topicProgress = this._topicProgress; - - // cache lookup - if (!$topicProgress) { - $topicProgress = $('#topic-progress'); - if (!$topicProgress.length) { - return; - } - this._topicProgress = $topicProgress; - // CAREFUL WITH THIS AXE - // offsetWidth will cause a reflow, this ensures it only happens once - // in future it may make sense to move this offscreen to do the measurement - Discourse.TopicView._progressWidth = Discourse.TopicView._progressWidth || $topicProgress[0].offsetWidth; - } - - // speeds up stuff, bypass jquery slowness and extra checks - var totalWidth = Discourse.TopicView._progressWidth, - progressWidth = this.get('controller.streamPercentage') * totalWidth; - - $topicProgress.find('.bg') - .css("border-right-width", (progressWidth === totalWidth) ? "0px" : "1px") - .width(progressWidth); - }, - _updateTitle: function() { var title = this.get('topic.title'); if (title) return Discourse.set('title', _.unescape(title)); @@ -64,8 +35,6 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { }.observes('composer'), _enteredTopic: function() { - this._topicProgress = undefined; - // Ember is supposed to only call observers when values change but something // in our view set up is firing this observer with the same value. This check // prevents scrolled from being called twice. @@ -84,21 +53,12 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { self.scrolled(); }); - // This get seems counter intuitive, but it's to trigger the observer on - // the streamPercentage for this view. Otherwise the process bar does not - // update. - this.get('controller.streamPercentage'); - this.$().on('mouseup.discourse-redirect', '.cooked a, a.track-link', function(e) { var $target = $(e.target); if ($target.hasClass('mention') || $target.parents('.expanded-embed').length) { return false; } return Discourse.ClickTrack.trackClick(e); }); - var dockProgressBar = function () { self._dockProgressBar(); }; - this.appEvents.on("composer:opened", dockProgressBar) - .on("composer:resized", dockProgressBar) - .on("composer:closed", dockProgressBar); }.on('didInsertElement'), // This view is being removed. Shut down operations @@ -114,10 +74,6 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { // this happens after route exit, stuff could have trickled in this.set('controller.controllers.header.showExtraInfo', false); - // unbind events - this.appEvents.off("composer:opened") - .off("composer:resized") - .off("composer:closed"); }.on('willDestroyElement'), debounceLoadSuggested: Discourse.debounce(function(){ @@ -181,45 +137,8 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { headerController.set('showExtraInfo', topic.get('postStream.firstPostNotLoaded')); } - // dock the counter if necessary - this._dockProgressBar(offset); - }, - - _dockProgressBar: function (offset) { - var maximumOffset = $('#topic-footer-buttons').offset(), - composerHeight = $('#reply-control').height() || 0, - $topicProgressWrapper = $('#topic-progress-wrapper'), - style = $topicProgressWrapper.attr('style') || '', - isDocked = false; - - offset = offset || window.pageYOffset || $('html').scrollTop(); - - if (maximumOffset) { - var 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 { - var height = composerHeight + "px"; - if ($topicProgressWrapper.css('bottom') !== height) { - $topicProgressWrapper.css('bottom', height); - } - } - } else { - if (style.indexOf('bottom') >= 0) { - $topicProgressWrapper.css('bottom', ''); - } - } - - this.set("controller.dockedCounter", isDocked); + // Trigger a scrolled event + this.appEvents.trigger('topic:scrolled'); }, topicTrackingState: function() {