diff --git a/app/assets/javascripts/discourse/controllers/topic_controller.js b/app/assets/javascripts/discourse/controllers/topic_controller.js index 041e33ab08f..ceef655d6d6 100644 --- a/app/assets/javascripts/discourse/controllers/topic_controller.js +++ b/app/assets/javascripts/discourse/controllers/topic_controller.js @@ -19,7 +19,7 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected }.property('postStream.filteredPostsCount', 'progressPosition'), jumpBottomDisabled: function() { - return this.get('progressPosition') === this.get('postStream.filteredPostsCount'); + return this.get('progressPosition') >= this.get('postStream.filteredPostsCount'); }.property('postStream.filteredPostsCount', 'progressPosition'), canMergeTopic: function() { @@ -60,6 +60,12 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected return canDelete; }.property('selectedPostsCount'), + streamPercentage: function() { + if (!this.get('postStream.loaded')) { return 0; } + if (this.get('postStream.filteredPostsCount') === 0) { return 0; } + return this.get('progressPosition') / this.get('postStream.filteredPostsCount'); + }.property('postStream.loaded', 'progressPosition', 'postStream.filteredPostsCount'), + multiSelectChanged: function() { // Deselect all posts when multi select is turned off if (!this.get('multiSelect')) { diff --git a/app/assets/javascripts/discourse/models/post_stream.js b/app/assets/javascripts/discourse/models/post_stream.js index bc6bb317a80..fe5be43c5ee 100644 --- a/app/assets/javascripts/discourse/models/post_stream.js +++ b/app/assets/javascripts/discourse/models/post_stream.js @@ -430,6 +430,30 @@ Discourse.PostStream = Em.Object.extend({ } }, + /** + Returns the closest post number given a postNumber that may not exist in the stream. + For example, if the user asks for a post that's deleted or otherwise outside the range. + This allows us to set the progress bar with the correct number. + + @method closestPostNumberFor + @param {Integer} postNumber the post number we're looking for + **/ + closestPostNumberFor: function(postNumber) { + if (!this.get('hasPosts')) { return; } + + var closest = null; + this.get('posts').forEach(function (p) { + if (closest === postNumber) { return; } + if (!closest) { closest = p.get('post_number'); } + + if (Math.abs(postNumber - p.get('post_number')) < Math.abs(closest - postNumber)) { + closest = p.get('post_number'); + } + }); + + return closest; + }, + /** @private 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 38f14bc259c..f8efd57556b 100644 --- a/app/assets/javascripts/discourse/routes/topic_from_params_route.js +++ b/app/assets/javascripts/discourse/routes/topic_from_params_route.js @@ -32,9 +32,13 @@ Discourse.TopicFromParamsRoute = Discourse.Route.extend({ var topicController = this.controllerFor('topic'); postStream.refresh(params).then(function () { + + // The post we requested might not exist. Let's find the closest post + var closest = postStream.closestPostNumberFor(params.nearPost) || 1; + topicController.setProperties({ - currentPost: params.nearPost || 1, - progressPosition: params.nearPost || 1 + currentPost: closest, + progressPosition: closest }); if (topic.present('draft')) { @@ -46,7 +50,6 @@ Discourse.TopicFromParamsRoute = Discourse.Route.extend({ ignoreIfChanged: true }); } - }); diff --git a/app/assets/javascripts/discourse/views/topic_view.js b/app/assets/javascripts/discourse/views/topic_view.js index 0722825a68c..339b3915787 100644 --- a/app/assets/javascripts/discourse/views/topic_view.js +++ b/app/assets/javascripts/discourse/views/topic_view.js @@ -17,22 +17,19 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { postStream: Em.computed.alias('controller.postStream'), - updateBar: function() { - if (!this.get('postStream.loaded')) return; + updateBar: function() { var $topicProgress = $('#topic-progress'); if (!$topicProgress.length) return; - var ratio = this.get('controller.progressPosition') / this.get('postStream.filteredPostsCount'); var totalWidth = $topicProgress.width(); - var progressWidth = ratio * totalWidth; - var bg = $topicProgress.find('.bg'); - var currentWidth = bg.width(); + var progressWidth = this.get('controller.streamPercentage') * totalWidth; - bg.css("border-right-width", (progressWidth === totalWidth) ? "0px" : "1px") - .width(progressWidth); + $topicProgress.find('.bg') + .css("border-right-width", (progressWidth === totalWidth) ? "0px" : "1px") + .width(progressWidth); - }.observes('controller.progressPosition', 'postStream.filteredPostsCount', 'topic.loaded'), + }.observes('controller.streamPercentage'), updateTitle: function() { var title = this.get('topic.title'); @@ -86,6 +83,9 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { }); this.updatePosition(); + + // We want to make sure the progress bar is updated after it's rendered + this.updateBar(); }, debounceLoadSuggested: Discourse.debounce(function(){ @@ -169,6 +169,7 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { if (!postView) return; var post = postView.get('post'); if (!post) return; + this.set('controller.progressPosition', this.get('postStream').indexOf(post) + 1); }, @@ -184,9 +185,14 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { this.updatePosition(); }, - updatePosition: function() { - var topic = this.get('controller.model'); + /** + Process the posts the current user has seen in the topic. + + @private + @method processSeenPosts + **/ + processSeenPosts: function() { var rows = $('.topic-post.ready'); if (!rows || rows.length === 0) { return; } @@ -235,6 +241,16 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { } else { console.error("can't update position "); } + }, + + /** + The user has scrolled the window, or it is finished rendering and ready for processing. + + @method updatePosition + **/ + updatePosition: function() { + + this.processSeenPosts(); var offset = window.pageYOffset || $('html').scrollTop(); if (!this.get('docAt')) { @@ -244,7 +260,8 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { } } - var headerController = this.get('controller.controllers.header'); + var headerController = this.get('controller.controllers.header'), + topic = this.get('controller.model'); if (this.get('docAt')) { headerController.set('showExtraInfo', offset >= this.get('docAt') || topic.get('postStream.firstPostNotLoaded')); } else { diff --git a/test/javascripts/models/post_stream_test.js b/test/javascripts/models/post_stream_test.js index 775444f4530..4fa37cfc105 100644 --- a/test/javascripts/models/post_stream_test.js +++ b/test/javascripts/models/post_stream_test.js @@ -54,6 +54,19 @@ test('appending posts', function() { ok(postStream.get('lastPostLoaded'), "the last post is still the last post in the new stream"); }); +test('closestPostNumberFor', function() { + var postStream = buildStream(1231); + + blank(postStream.closestPostNumberFor(1), "there is no closest post when nothing is loaded"); + + postStream.appendPost(Discourse.Post.create({id: 1, post_number: 2})); + postStream.appendPost(Discourse.Post.create({id: 2, post_number: 3})); + + equal(postStream.closestPostNumberFor(2), 2, "If a post is in the stream it returns its post number"); + equal(postStream.closestPostNumberFor(3), 3, "If a post is in the stream it returns its post number"); + equal(postStream.closestPostNumberFor(10), 3, "it clips to the upper bound of the stream"); + equal(postStream.closestPostNumberFor(0), 2, "it clips to the lower bound of the stream"); +}); test('updateFromJson', function() { var postStream = buildStream(1231);