diff --git a/app/assets/javascripts/discourse/components/post_gap_component.js b/app/assets/javascripts/discourse/components/post_gap_component.js new file mode 100644 index 000000000..651888555 --- /dev/null +++ b/app/assets/javascripts/discourse/components/post_gap_component.js @@ -0,0 +1,48 @@ +/** + Handles a gap between posts with a click to load more + + @class PostGapComponent + @extends Ember.Component + @namespace Discourse + @module Discourse +**/ +Discourse.PostGapComponent = Ember.Component.extend({ + classNameBindings: [':gap', 'gap::hidden'], + + init: function() { + this._super(); + this.set('loading', false); + + var before = this.get('before') === 'true', + gaps = before ? this.get('postStream.gaps.before') : this.get('postStream.gaps.after'); + + if (gaps) { + this.set('gap', gaps[this.get('post.id')]); + } + }, + + render: function(buffer) { + if (this.get('loading')) { + buffer.push(I18n.t('loading')); + } else { + buffer.push("" + I18n.t('post.gap', {count: this.get('gap.length')})); + } + }, + + click: function() { + if (this.get('loading') || (!this.get('gap'))) { return false; } + this.set('loading', true); + this.rerender(); + + var self = this, + postStream = this.get('postStream'), + filler = this.get('before') === 'true' ? postStream.fillGapBefore : postStream.fillGapAfter; + + filler.call(postStream, this.get('post'), this.get('gap')).then(function() { + // hide this control after the promise is resolved + self.set('gap', null); + }); + + return false; + } +}); diff --git a/app/assets/javascripts/discourse/controllers/topic_controller.js b/app/assets/javascripts/discourse/controllers/topic_controller.js index 5fffb498a..949c6d38b 100644 --- a/app/assets/javascripts/discourse/controllers/topic_controller.js +++ b/app/assets/javascripts/discourse/controllers/topic_controller.js @@ -500,6 +500,12 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected } }, + /** + Called the the topmost visible post on the page changes. + + @method topVisibleChanged + @params {Discourse.Post} post that is at the top + **/ topVisibleChanged: function(post) { var postStream = this.get('postStream'), firstLoadedPost = postStream.get('firstLoadedPost'); @@ -523,11 +529,18 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected } }, - bottomVisibleChanged: function(post) { - this.set('progressPosition', post.get('post_number')); + /** + Called the the bottommost visible post on the page changes. + @method bottomVisibleChanged + @params {Discourse.Post} post that is at the bottom + **/ + bottomVisibleChanged: function(post) { var postStream = this.get('postStream'), - lastLoadedPost = postStream.get('lastLoadedPost'); + lastLoadedPost = postStream.get('lastLoadedPost'), + index = postStream.get('stream').indexOf(post.get('id'))+1; + + this.set('progressPosition', index); if (lastLoadedPost && lastLoadedPost === post) { postStream.appendMore(); diff --git a/app/assets/javascripts/discourse/models/post_stream.js b/app/assets/javascripts/discourse/models/post_stream.js index 37b8370c2..0ce0af091 100644 --- a/app/assets/javascripts/discourse/models/post_stream.js +++ b/app/assets/javascripts/discourse/models/post_stream.js @@ -126,31 +126,11 @@ Discourse.PostStream = Em.Object.extend({ return result; }.property('userFilters.[]', 'summary'), - /** - The text describing the current filters. For display in the pop up at the bottom of the - screen. - - @property filterDesc - **/ - filterDesc: function() { + hasNoFilters: function() { var streamFilters = this.get('streamFilters'); - - if (streamFilters.filter && streamFilters.filter === "summary") { - return I18n.t("topic.filters.summary", { - n_summarized_posts: I18n.t("topic.filters.n_summarized_posts", { count: this.get('filteredPostsCount') }), - of_n_posts: I18n.t("topic.filters.of_n_posts", { count: this.get('topic.posts_count') }) - }); - } else if (streamFilters.username_filters) { - return I18n.t("topic.filters.user", { - n_posts: I18n.t("topic.filters.n_posts", { count: this.get('filteredPostsCount') }), - by_n_users: I18n.t("topic.filters.by_n_users", { count: streamFilters.username_filters.length }) - }); - } - return ""; + return !(streamFilters && ((streamFilters.filter === 'summary') || streamFilters.userFilters)); }.property('streamFilters.[]', 'topic.posts_count', 'posts.length'), - hasNoFilters: Em.computed.empty('filterDesc'), - /** Returns the window of posts above the current set in the stream, bound to the top of the stream. This is the collection we'll ask for when scrolling upwards. @@ -274,6 +254,66 @@ Discourse.PostStream = Em.Object.extend({ }, hasLoadedData: Em.computed.and('hasPosts', 'hasStream'), + + /** + Fill in a gap of posts before a particular post + + @method fillGapBefore + @paaram {Discourse.Post} post beside gap + @paaram {Array} gap array of post ids to load + @returns {Ember.Deferred} a promise that's resolved when the posts have been added. + **/ + fillGapBefore: function(post, gap) { + var postId = post.get('id'), + stream = this.get('stream'), + idx = stream.indexOf(postId), + currentPosts = this.get('posts'), + self = this; + + if (idx !== -1) { + // Insert the gap at the appropriate place + stream.splice.apply(stream, [idx, 0].concat(gap)); + stream.enumerableContentDidChange(); + + var postIdx = currentPosts.indexOf(post); + if (postIdx !== -1) { + return this.findPostsByIds(gap).then(function(posts) { + posts.forEach(function(p) { + var stored = self.storePost(p); + if (!currentPosts.contains(stored)) { + currentPosts.insertAt(postIdx++, stored); + } + }); + + delete self.get('gaps.before')[postId]; + }); + } + } + return Ember.RSVP.resolve(); + }, + + /** + Fill in a gap of posts after a particular post + + @method fillGapAfter + @paaram {Discourse.Post} post beside gap + @paaram {Array} gap array of post ids to load + @returns {Ember.Deferred} a promise that's resolved when the posts have been added. + **/ + fillGapAfter: function(post, gap) { + var postId = post.get('id'), + stream = this.get('stream'), + idx = stream.indexOf(postId), + currentPosts = this.get('posts'), + self = this; + + if (idx !== -1) { + stream.pushObjects(gap); + return this.appendMore(); + } + return Ember.RSVP.resolve(); + }, + /** Appends the next window of posts to the stream. Call it when scrolling downwards. @@ -522,9 +562,9 @@ Discourse.PostStream = Em.Object.extend({ @method updateFromJson **/ updateFromJson: function(postStreamData) { - var postStream = this; + var postStream = this, + posts = this.get('posts'); - var posts = this.get('posts'); posts.clear(); if (postStreamData) { // Load posts if present diff --git a/app/assets/javascripts/discourse/templates/post.js.handlebars b/app/assets/javascripts/discourse/templates/post.js.handlebars index d07ccb7e3..097457d9a 100644 --- a/app/assets/javascripts/discourse/templates/post.js.handlebars +++ b/app/assets/javascripts/discourse/templates/post.js.handlebars @@ -1,3 +1,5 @@ +{{post-gap post=this postStream=controller.postStream before="true"}} +
{{view Discourse.ReplyHistory contentBinding="replyHistory"}}
@@ -82,3 +84,5 @@ + +{{post-gap post=this postStream=controller.postStream before="false"}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/templates/topic.js.handlebars b/app/assets/javascripts/discourse/templates/topic.js.handlebars index 8f4627b62..bd3b4a7bd 100644 --- a/app/assets/javascripts/discourse/templates/topic.js.handlebars +++ b/app/assets/javascripts/discourse/templates/topic.js.handlebars @@ -52,7 +52,7 @@