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 @@