FEATURE: Reply Placeholders in Stream
This commit is contained in:
parent
1987a35daf
commit
40c8d39137
|
@ -115,7 +115,7 @@ export default Ember.Controller.extend({
|
||||||
|
|
||||||
// If there is no current post, use the first post id from the stream
|
// If there is no current post, use the first post id from the stream
|
||||||
if (!postId && postStream) {
|
if (!postId && postStream) {
|
||||||
postId = postStream.get('firstPostId');
|
postId = postStream.get('stream.firstObject');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're editing a post, fetch the reply when importing a quote
|
// If we're editing a post, fetch the reply when importing a quote
|
||||||
|
|
|
@ -668,8 +668,8 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
topVisibleChanged(post) {
|
topVisibleChanged(post) {
|
||||||
if (!post) { return; }
|
if (!post) { return; }
|
||||||
|
|
||||||
const postStream = this.get('model.postStream'),
|
const postStream = this.get('model.postStream');
|
||||||
firstLoadedPost = postStream.get('firstLoadedPost');
|
const firstLoadedPost = postStream.get('posts.firstObject');
|
||||||
|
|
||||||
this.set('model.currentPost', post.get('post_number'));
|
this.set('model.currentPost', post.get('post_number'));
|
||||||
|
|
||||||
|
@ -680,15 +680,16 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
// trigger a scroll after a promise resolves in a controller? We need
|
// trigger a scroll after a promise resolves in a controller? We need
|
||||||
// to do this to preserve upwards infinte scrolling.
|
// to do this to preserve upwards infinte scrolling.
|
||||||
const $body = $('body');
|
const $body = $('body');
|
||||||
let $elem = $('#post-cloak-' + post.get('post_number'));
|
const elemId = `#post_${post.get('post_number')}`;
|
||||||
|
const $elem = $(elemId).closest('.post-cloak');
|
||||||
const distToElement = $body.scrollTop() - $elem.position().top;
|
const distToElement = $body.scrollTop() - $elem.position().top;
|
||||||
|
|
||||||
postStream.prependMore().then(function() {
|
postStream.prependMore().then(function() {
|
||||||
Em.run.next(function () {
|
Em.run.next(function () {
|
||||||
$elem = $('#post-cloak-' + post.get('post_number'));
|
const $refreshedElem = $(elemId).closest('.post-cloak');
|
||||||
|
|
||||||
// Quickly going back might mean the element is destroyed
|
// Quickly going back might mean the element is destroyed
|
||||||
const position = $elem.position();
|
const position = $refreshedElem.position();
|
||||||
if (position && position.top) {
|
if (position && position.top) {
|
||||||
$('html, body').scrollTop(position.top + distToElement);
|
$('html, body').scrollTop(position.top + distToElement);
|
||||||
}
|
}
|
||||||
|
@ -706,8 +707,8 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
bottomVisibleChanged(post) {
|
bottomVisibleChanged(post) {
|
||||||
if (!post) { return; }
|
if (!post) { return; }
|
||||||
|
|
||||||
const postStream = this.get('model.postStream'),
|
const postStream = this.get('model.postStream');
|
||||||
lastLoadedPost = postStream.get('lastLoadedPost');
|
const lastLoadedPost = postStream.get('posts.lastObject');
|
||||||
|
|
||||||
this.set('controllers.topic-progress.progressPosition', postStream.progressIndexOfPost(post));
|
this.set('controllers.topic-progress.progressPosition', postStream.progressIndexOfPost(post));
|
||||||
|
|
||||||
|
|
|
@ -15,14 +15,13 @@ const DiscourseURL = Ember.Object.createWithMixins({
|
||||||
Jumps to a particular post in the stream
|
Jumps to a particular post in the stream
|
||||||
**/
|
**/
|
||||||
jumpToPost: function(postNumber, opts) {
|
jumpToPost: function(postNumber, opts) {
|
||||||
const holderId = '#post-cloak-' + postNumber;
|
const holderId = `#post_${postNumber}`;
|
||||||
|
const offset = function() {
|
||||||
|
|
||||||
const offset = function(){
|
const $header = $('header');
|
||||||
|
const $title = $('#topic-title');
|
||||||
const $header = $('header'),
|
const windowHeight = $(window).height() - $title.height();
|
||||||
$title = $('#topic-title'),
|
const expectedOffset = $title.height() - $header.find('.contents').height() + (windowHeight / 5);
|
||||||
windowHeight = $(window).height() - $title.height(),
|
|
||||||
expectedOffset = $title.height() - $header.find('.contents').height() + (windowHeight / 5);
|
|
||||||
|
|
||||||
return $header.outerHeight(true) + ((expectedOffset < 0) ? 0 : expectedOffset);
|
return $header.outerHeight(true) + ((expectedOffset < 0) ? 0 : expectedOffset);
|
||||||
};
|
};
|
||||||
|
@ -203,40 +202,40 @@ const DiscourseURL = Ember.Object.createWithMixins({
|
||||||
@param {String} oldPath the previous path we were on
|
@param {String} oldPath the previous path we were on
|
||||||
@param {String} path the path we're navigating to
|
@param {String} path the path we're navigating to
|
||||||
**/
|
**/
|
||||||
navigatedToPost: function(oldPath, path) {
|
navigatedToPost(oldPath, path) {
|
||||||
const newMatches = this.TOPIC_REGEXP.exec(path),
|
const newMatches = this.TOPIC_REGEXP.exec(path);
|
||||||
newTopicId = newMatches ? newMatches[2] : null;
|
const newTopicId = newMatches ? newMatches[2] : null;
|
||||||
|
|
||||||
if (newTopicId) {
|
if (newTopicId) {
|
||||||
const oldMatches = this.TOPIC_REGEXP.exec(oldPath),
|
const oldMatches = this.TOPIC_REGEXP.exec(oldPath);
|
||||||
oldTopicId = oldMatches ? oldMatches[2] : null;
|
const oldTopicId = oldMatches ? oldMatches[2] : null;
|
||||||
|
|
||||||
// If the topic_id is the same
|
// If the topic_id is the same
|
||||||
if (oldTopicId === newTopicId) {
|
if (oldTopicId === newTopicId) {
|
||||||
DiscourseURL.replaceState(path);
|
DiscourseURL.replaceState(path);
|
||||||
|
|
||||||
const container = Discourse.__container__,
|
const container = Discourse.__container__;
|
||||||
topicController = container.lookup('controller:topic'),
|
const topicController = container.lookup('controller:topic');
|
||||||
opts = {},
|
const opts = {};
|
||||||
postStream = topicController.get('model.postStream');
|
const postStream = topicController.get('model.postStream');
|
||||||
|
|
||||||
if (newMatches[3]) opts.nearPost = newMatches[3];
|
if (newMatches[3]) { opts.nearPost = newMatches[3]; }
|
||||||
if (path.match(/last$/)) { opts.nearPost = topicController.get('model.highest_post_number'); }
|
if (path.match(/last$/)) { opts.nearPost = topicController.get('model.highest_post_number'); }
|
||||||
const closest = opts.nearPost || 1;
|
const closest = opts.nearPost || 1;
|
||||||
|
|
||||||
const self = this;
|
postStream.refresh(opts).then(() => {
|
||||||
postStream.refresh(opts).then(function() {
|
|
||||||
topicController.setProperties({
|
topicController.setProperties({
|
||||||
'model.currentPost': closest,
|
'model.currentPost': closest,
|
||||||
enteredAt: new Date().getTime().toString()
|
enteredAt: new Date().getTime().toString()
|
||||||
});
|
});
|
||||||
const closestPost = postStream.closestPostForPostNumber(closest),
|
|
||||||
progress = postStream.progressIndexOfPost(closestPost),
|
const closestPost = postStream.closestPostForPostNumber(closest);
|
||||||
progressController = container.lookup('controller:topic-progress');
|
const progress = postStream.progressIndexOfPost(closestPost);
|
||||||
|
const progressController = container.lookup('controller:topic-progress');
|
||||||
|
|
||||||
progressController.set('progressPosition', progress);
|
progressController.set('progressPosition', progress);
|
||||||
self.appEvents.trigger('post:highlight', closest);
|
this.appEvents.trigger('post:highlight', closest);
|
||||||
}).then(function() {
|
}).then(() => {
|
||||||
DiscourseURL.jumpToPost(closest, {skipIfOnScreen: true});
|
DiscourseURL.jumpToPost(closest, {skipIfOnScreen: true});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import DiscourseURL from 'discourse/lib/url';
|
import DiscourseURL from 'discourse/lib/url';
|
||||||
import RestModel from 'discourse/models/rest';
|
import RestModel from 'discourse/models/rest';
|
||||||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||||
|
import { Placeholder } from 'discourse/views/cloaked';
|
||||||
|
|
||||||
function calcDayDiff(p1, p2) {
|
function calcDayDiff(p1, p2) {
|
||||||
if (!p1) { return; }
|
if (!p1) { return; }
|
||||||
|
@ -17,7 +18,119 @@ function calcDayDiff(p1, p2) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostStream = RestModel.extend({
|
export function loadTopicView(topic, args) {
|
||||||
|
const topicId = topic.get('id');
|
||||||
|
const data = _.merge({}, args);
|
||||||
|
const url = Discourse.getURL("/t/") + topicId;
|
||||||
|
const jsonUrl = (data.nearPost ? `${url}/${data.nearPost}` : url) + '.json';
|
||||||
|
|
||||||
|
delete data.nearPost;
|
||||||
|
delete data.__type;
|
||||||
|
delete data.store;
|
||||||
|
|
||||||
|
return PreloadStore.getAndRemove(`topic_${topicId}`, () => {
|
||||||
|
return Discourse.ajax(jsonUrl, {data});
|
||||||
|
}).then(json => {
|
||||||
|
topic.updateFromJson(json);
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const PostsWithPlaceholders = Ember.Object.extend(Ember.Array, {
|
||||||
|
posts: null,
|
||||||
|
_appendingIds: null,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._appendingIds = {};
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed
|
||||||
|
length() {
|
||||||
|
return this.get('posts.length') + Object.keys(this._appendingIds || {}).length;
|
||||||
|
},
|
||||||
|
|
||||||
|
append(cb) {
|
||||||
|
const l = this.get('posts.length');
|
||||||
|
this.arrayContentWillChange(l, 0, 1);
|
||||||
|
cb();
|
||||||
|
this.arrayContentDidChange(l, 0, 1);
|
||||||
|
this.propertyDidChange('length');
|
||||||
|
},
|
||||||
|
|
||||||
|
removePost(cb) {
|
||||||
|
const l = this.get('posts.length') - 1;
|
||||||
|
this.arrayContentWillChange(l, 1, 0);
|
||||||
|
cb();
|
||||||
|
this.arrayContentDidChange(l, 1, 0);
|
||||||
|
this.propertyDidChange('length');
|
||||||
|
},
|
||||||
|
|
||||||
|
appending(postIds) {
|
||||||
|
console.log('appending');
|
||||||
|
const l = this.get('length');
|
||||||
|
this.arrayContentWillChange(l, 0, postIds.length);
|
||||||
|
const appendingIds = this._appendingIds;
|
||||||
|
postIds.forEach(pid => appendingIds[pid] = true);
|
||||||
|
this.arrayContentDidChange(l, 0, postIds.length);
|
||||||
|
this.propertyDidChange('length');
|
||||||
|
},
|
||||||
|
|
||||||
|
finishedAppending(postIds) {
|
||||||
|
const l = this.get('posts.length') - postIds.length;
|
||||||
|
this.arrayContentWillChange(l, postIds.length, postIds.length);
|
||||||
|
const appendingIds = this._appendingIds;
|
||||||
|
postIds.forEach(pid => delete appendingIds[pid]);
|
||||||
|
this.arrayContentDidChange(l, postIds.length, postIds.length);
|
||||||
|
this.propertyDidChange('length');
|
||||||
|
},
|
||||||
|
|
||||||
|
finishedPrepending(postIds) {
|
||||||
|
this.arrayContentDidChange(0, 0, postIds.length);
|
||||||
|
this.propertyDidChange('length');
|
||||||
|
},
|
||||||
|
|
||||||
|
objectAt(index) {
|
||||||
|
const posts = this.get('posts');
|
||||||
|
if (index < posts.length) {
|
||||||
|
return posts[index];
|
||||||
|
} else {
|
||||||
|
return new Placeholder('post-placeholder');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default RestModel.extend({
|
||||||
|
_identityMap: null,
|
||||||
|
posts: null,
|
||||||
|
stream: null,
|
||||||
|
userFilters: null,
|
||||||
|
summary: null,
|
||||||
|
loaded: null,
|
||||||
|
loadingAbove: null,
|
||||||
|
loadingBelow: null,
|
||||||
|
loadingFilter: null,
|
||||||
|
stagingPost: null,
|
||||||
|
postsWithPlaceholders: null,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._identityMap = {};
|
||||||
|
const posts = [];
|
||||||
|
const postsWithPlaceholders = PostsWithPlaceholders.create({ posts, store: this.store });
|
||||||
|
|
||||||
|
this.setProperties({
|
||||||
|
posts,
|
||||||
|
postsWithPlaceholders,
|
||||||
|
stream: [],
|
||||||
|
userFilters: [],
|
||||||
|
summary: false,
|
||||||
|
loaded: false,
|
||||||
|
loadingAbove: false,
|
||||||
|
loadingBelow: false,
|
||||||
|
loadingFilter: false,
|
||||||
|
stagingPost: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
loading: Ember.computed.or('loadingAbove', 'loadingBelow', 'loadingFilter', 'stagingPost'),
|
loading: Ember.computed.or('loadingAbove', 'loadingBelow', 'loadingFilter', 'stagingPost'),
|
||||||
notLoading: Ember.computed.not('loading'),
|
notLoading: Ember.computed.not('loading'),
|
||||||
filteredPostsCount: Ember.computed.alias("stream.length"),
|
filteredPostsCount: Ember.computed.alias("stream.length"),
|
||||||
|
@ -27,7 +140,11 @@ const PostStream = RestModel.extend({
|
||||||
return this.get('posts.length') > 0;
|
return this.get('posts.length') > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
hasStream: Ember.computed.gt('filteredPostsCount', 0),
|
@computed('hasPosts', 'filteredPostsCount')
|
||||||
|
hasLoadedData(hasPosts, filteredPostsCount) {
|
||||||
|
return hasPosts && filteredPostsCount > 0;
|
||||||
|
},
|
||||||
|
|
||||||
canAppendMore: Ember.computed.and('notLoading', 'hasPosts', 'lastPostNotLoaded'),
|
canAppendMore: Ember.computed.and('notLoading', 'hasPosts', 'lastPostNotLoaded'),
|
||||||
canPrependMore: Ember.computed.and('notLoading', 'hasPosts', 'firstPostNotLoaded'),
|
canPrependMore: Ember.computed.and('notLoading', 'hasPosts', 'firstPostNotLoaded'),
|
||||||
|
|
||||||
|
@ -38,26 +155,8 @@ const PostStream = RestModel.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
firstPostNotLoaded: Ember.computed.not('firstPostPresent'),
|
firstPostNotLoaded: Ember.computed.not('firstPostPresent'),
|
||||||
|
firstPostId: Ember.computed.alias('stream.firstObject'),
|
||||||
@computed('posts.@each')
|
lastPostId: Ember.computed.alias('stream.lastObject'),
|
||||||
firstLoadedPost() {
|
|
||||||
return _.first(this.get('posts'));
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed('posts.@each')
|
|
||||||
lastLoadedPost() {
|
|
||||||
return _.last(this.get('posts'));
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed('stream.@each')
|
|
||||||
firstPostId() {
|
|
||||||
return this.get('stream')[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed('stream.@each')
|
|
||||||
lastPostId() {
|
|
||||||
return _.last(this.get('stream'));
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed('hasLoadedData', 'lastPostId', 'posts.@each.id')
|
@computed('hasLoadedData', 'lastPostId', 'posts.@each.id')
|
||||||
loadedAllPosts(hasLoadedData, lastPostId) {
|
loadedAllPosts(hasLoadedData, lastPostId) {
|
||||||
|
@ -117,7 +216,7 @@ const PostStream = RestModel.extend({
|
||||||
Returns the window of posts below the current set in the stream, bound by the bottom of the
|
Returns the window of posts below the current set in the stream, bound by the bottom of the
|
||||||
stream. This is the collection we use when scrolling downwards.
|
stream. This is the collection we use when scrolling downwards.
|
||||||
**/
|
**/
|
||||||
@computed('lastLoadedPost', 'stream.@each')
|
@computed('posts.lastObject', 'stream.@each')
|
||||||
nextWindow(lastLoadedPost) {
|
nextWindow(lastLoadedPost) {
|
||||||
// If we can't find the last post loaded, bail
|
// If we can't find the last post loaded, bail
|
||||||
if (!lastLoadedPost) { return []; }
|
if (!lastLoadedPost) { return []; }
|
||||||
|
@ -206,8 +305,7 @@ const PostStream = RestModel.extend({
|
||||||
opts = _.merge(opts, this.get('streamFilters'));
|
opts = _.merge(opts, this.get('streamFilters'));
|
||||||
|
|
||||||
// Request a topicView
|
// Request a topicView
|
||||||
return PostStream.loadTopicView(topic.get('id'), opts).then(json => {
|
return loadTopicView(topic, opts).then(json => {
|
||||||
topic.updateFromJson(json);
|
|
||||||
this.updateFromJson(json.post_stream);
|
this.updateFromJson(json.post_stream);
|
||||||
this.setProperties({ loadingFilter: false, loaded: true });
|
this.setProperties({ loadingFilter: false, loaded: true });
|
||||||
}).catch(result => {
|
}).catch(result => {
|
||||||
|
@ -215,7 +313,6 @@ const PostStream = RestModel.extend({
|
||||||
throw result;
|
throw result;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
hasLoadedData: Ember.computed.and('hasPosts', 'hasStream'),
|
|
||||||
|
|
||||||
collapsePosts(from, to){
|
collapsePosts(from, to){
|
||||||
const posts = this.get('posts');
|
const posts = this.get('posts');
|
||||||
|
@ -237,7 +334,6 @@ const PostStream = RestModel.extend({
|
||||||
this.get('stream').enumerableContentDidChange();
|
this.get('stream').enumerableContentDidChange();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Fill in a gap of posts before a particular post
|
// Fill in a gap of posts before a particular post
|
||||||
fillGapBefore(post, gap) {
|
fillGapBefore(post, gap) {
|
||||||
const postId = post.get('id'),
|
const postId = post.get('id'),
|
||||||
|
@ -293,12 +389,15 @@ const PostStream = RestModel.extend({
|
||||||
|
|
||||||
this.set('loadingBelow', true);
|
this.set('loadingBelow', true);
|
||||||
|
|
||||||
const stopLoading = () => this.set('loadingBelow', false);
|
const postsWithPlaceholders = this.get('postsWithPlaceholders');
|
||||||
|
postsWithPlaceholders.appending(postIds);
|
||||||
return this.findPostsByIds(postIds).then((posts) => {
|
return this.findPostsByIds(postIds).then(posts => {
|
||||||
posts.forEach(p => this.appendPost(p));
|
posts.forEach(p => this.appendPost(p));
|
||||||
stopLoading();
|
return posts;
|
||||||
}, stopLoading);
|
}).finally(() => {
|
||||||
|
postsWithPlaceholders.finishedAppending(postIds);
|
||||||
|
this.set('loadingBelow', false);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Prepend the previous window of posts to the stream. Call it when scrolling upwards.
|
// Prepend the previous window of posts to the stream. Call it when scrolling upwards.
|
||||||
|
@ -312,6 +411,9 @@ const PostStream = RestModel.extend({
|
||||||
this.set('loadingAbove', true);
|
this.set('loadingAbove', true);
|
||||||
return this.findPostsByIds(postIds.reverse()).then(posts => {
|
return this.findPostsByIds(postIds.reverse()).then(posts => {
|
||||||
posts.forEach(p => this.prependPost(p));
|
posts.forEach(p => this.prependPost(p));
|
||||||
|
}).finally(() => {
|
||||||
|
const postsWithPlaceholders = this.get('postsWithPlaceholders');
|
||||||
|
postsWithPlaceholders.finishedPrepending(postIds);
|
||||||
this.set('loadingAbove', false);
|
this.set('loadingAbove', false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -363,8 +465,7 @@ const PostStream = RestModel.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
this.get('stream').removeObject(-1);
|
this.get('stream').removeObject(-1);
|
||||||
this.get('postIdentityMap').set(-1, null);
|
this._identityMap[-1] = null;
|
||||||
|
|
||||||
this.set('stagingPost', false);
|
this.set('stagingPost', false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -374,8 +475,8 @@ const PostStream = RestModel.extend({
|
||||||
**/
|
**/
|
||||||
undoPost(post) {
|
undoPost(post) {
|
||||||
this.get('stream').removeObject(-1);
|
this.get('stream').removeObject(-1);
|
||||||
this.posts.removeObject(post);
|
this.get('postsWithPlaceholders').removePost(() => this.posts.removeObject(post));
|
||||||
this.get('postIdentityMap').set(-1, null);
|
this._identityMap[-1] = null;
|
||||||
|
|
||||||
const topic = this.get('topic');
|
const topic = this.get('topic');
|
||||||
this.set('stagingPost', false);
|
this.set('stagingPost', false);
|
||||||
|
@ -405,7 +506,13 @@ const PostStream = RestModel.extend({
|
||||||
const posts = this.get('posts');
|
const posts = this.get('posts');
|
||||||
|
|
||||||
calcDayDiff(stored, this.get('lastAppended'));
|
calcDayDiff(stored, this.get('lastAppended'));
|
||||||
posts.addObject(stored);
|
if (!posts.contains(stored)) {
|
||||||
|
if (!this.get('loadingBelow')) {
|
||||||
|
this.get('postsWithPlaceholders').append(() => posts.pushObject(stored));
|
||||||
|
} else {
|
||||||
|
posts.pushObject(stored);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (stored.get('id') !== -1) {
|
if (stored.get('id') !== -1) {
|
||||||
this.set('lastAppended', stored);
|
this.set('lastAppended', stored);
|
||||||
|
@ -418,16 +525,16 @@ const PostStream = RestModel.extend({
|
||||||
if (Ember.isEmpty(posts)) { return; }
|
if (Ember.isEmpty(posts)) { return; }
|
||||||
|
|
||||||
const postIds = posts.map(p => p.get('id'));
|
const postIds = posts.map(p => p.get('id'));
|
||||||
const identityMap = this.get('postIdentityMap');
|
const identityMap = this._identityMap;
|
||||||
|
|
||||||
this.get('stream').removeObjects(postIds);
|
this.get('stream').removeObjects(postIds);
|
||||||
this.get('posts').removeObjects(posts);
|
this.get('posts').removeObjects(posts);
|
||||||
postIds.forEach(id => identityMap.delete(id));
|
postIds.forEach(id => delete identityMap[id]);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Returns a post from the identity map if it's been inserted.
|
// Returns a post from the identity map if it's been inserted.
|
||||||
findLoadedPost(id) {
|
findLoadedPost(id) {
|
||||||
return this.get('postIdentityMap').get(id);
|
return this._identityMap[id];
|
||||||
},
|
},
|
||||||
|
|
||||||
loadPost(postId){
|
loadPost(postId){
|
||||||
|
@ -454,16 +561,13 @@ const PostStream = RestModel.extend({
|
||||||
this.get('stream').addObject(postId);
|
this.get('stream').addObject(postId);
|
||||||
if (loadedAllPosts) {
|
if (loadedAllPosts) {
|
||||||
this.set('loadingLastPost', true);
|
this.set('loadingLastPost', true);
|
||||||
this.appendMore().finally(
|
this.appendMore().finally(()=> this.set('loadingLastPost', true));
|
||||||
()=>this.set('loadingLastPost', true)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
triggerRecoveredPost(postId) {
|
triggerRecoveredPost(postId) {
|
||||||
const postIdentityMap = this.get('postIdentityMap');
|
const existing = this._identityMap[postId];
|
||||||
const existing = postIdentityMap.get(postId);
|
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
this.triggerChangedPost(postId, new Date());
|
this.triggerChangedPost(postId, new Date());
|
||||||
|
@ -506,8 +610,7 @@ const PostStream = RestModel.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
triggerDeletedPost(postId){
|
triggerDeletedPost(postId){
|
||||||
const postIdentityMap = this.get('postIdentityMap');
|
const existing = this._identityMap[postId];
|
||||||
const existing = postIdentityMap.get(postId);
|
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
const url = "/posts/" + postId;
|
const url = "/posts/" + postId;
|
||||||
|
@ -524,8 +627,7 @@ const PostStream = RestModel.extend({
|
||||||
triggerChangedPost(postId, updatedAt) {
|
triggerChangedPost(postId, updatedAt) {
|
||||||
if (!postId) { return; }
|
if (!postId) { return; }
|
||||||
|
|
||||||
const postIdentityMap = this.get('postIdentityMap');
|
const existing = this._identityMap[postId];
|
||||||
const existing = postIdentityMap.get(postId);
|
|
||||||
if (existing && existing.updated_at !== updatedAt) {
|
if (existing && existing.updated_at !== updatedAt) {
|
||||||
const url = "/posts/" + postId;
|
const url = "/posts/" + postId;
|
||||||
const store = this.store;
|
const store = this.store;
|
||||||
|
@ -625,19 +727,18 @@ const PostStream = RestModel.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
updateFromJson(postStreamData) {
|
updateFromJson(postStreamData) {
|
||||||
const postStream = this,
|
const posts = this.get('posts');
|
||||||
posts = this.get('posts');
|
|
||||||
|
|
||||||
posts.clear();
|
posts.clear();
|
||||||
this.set('gaps', null);
|
this.set('gaps', null);
|
||||||
if (postStreamData) {
|
if (postStreamData) {
|
||||||
// Load posts if present
|
// Load posts if present
|
||||||
const store = this.store;
|
const store = this.store;
|
||||||
postStreamData.posts.forEach(p => postStream.appendPost(store.createRecord('post', p)));
|
postStreamData.posts.forEach(p => this.appendPost(store.createRecord('post', p)));
|
||||||
delete postStreamData.posts;
|
delete postStreamData.posts;
|
||||||
|
|
||||||
// Update our attributes
|
// Update our attributes
|
||||||
postStream.setProperties(postStreamData);
|
this.setProperties(postStreamData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -647,13 +748,12 @@ const PostStream = RestModel.extend({
|
||||||
than you supplied if the post has already been loaded.
|
than you supplied if the post has already been loaded.
|
||||||
**/
|
**/
|
||||||
storePost(post) {
|
storePost(post) {
|
||||||
// Calling `Ember.get(undefined` raises an error
|
// Calling `Ember.get(undefined)` raises an error
|
||||||
if (!post) { return; }
|
if (!post) { return; }
|
||||||
|
|
||||||
const postId = Ember.get(post, 'id');
|
const postId = Ember.get(post, 'id');
|
||||||
if (postId) {
|
if (postId) {
|
||||||
const postIdentityMap = this.get('postIdentityMap'),
|
const existing = this._identityMap[post.get('id')];
|
||||||
existing = postIdentityMap.get(post.get('id'));
|
|
||||||
|
|
||||||
// Update the `highest_post_number` if this post is higher.
|
// Update the `highest_post_number` if this post is higher.
|
||||||
const postNumber = post.get('post_number');
|
const postNumber = post.get('post_number');
|
||||||
|
@ -668,31 +768,18 @@ const PostStream = RestModel.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
post.set('topic', this.get('topic'));
|
post.set('topic', this.get('topic'));
|
||||||
postIdentityMap.set(post.get('id'), post);
|
this._identityMap[post.get('id')] = post;
|
||||||
}
|
}
|
||||||
return post;
|
return post;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
Given a list of postIds, returns a list of the posts we don't have in our
|
|
||||||
identity map and need to load.
|
|
||||||
**/
|
|
||||||
listUnloadedIds(postIds) {
|
|
||||||
const unloaded = [];
|
|
||||||
const postIdentityMap = this.get('postIdentityMap');
|
|
||||||
postIds.forEach(p => {
|
|
||||||
if (!postIdentityMap.has(p)) { unloaded.pushObject(p); }
|
|
||||||
});
|
|
||||||
return unloaded;
|
|
||||||
},
|
|
||||||
|
|
||||||
findPostsByIds(postIds) {
|
findPostsByIds(postIds) {
|
||||||
const unloaded = this.listUnloadedIds(postIds);
|
const identityMap = this._identityMap;
|
||||||
const postIdentityMap = this.get('postIdentityMap');
|
const unloaded = postIds.filter(p => !identityMap[p]);
|
||||||
|
|
||||||
// Load our unloaded posts by id
|
// Load our unloaded posts by id
|
||||||
return this.loadIntoIdentityMap(unloaded).then(() => {
|
return this.loadIntoIdentityMap(unloaded).then(() => {
|
||||||
return postIds.map(p => postIdentityMap.get(p)).compact();
|
return postIds.map(p => identityMap[p]).compact();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -747,40 +834,3 @@ const PostStream = RestModel.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
PostStream.reopenClass({
|
|
||||||
create() {
|
|
||||||
const postStream = this._super.apply(this, arguments);
|
|
||||||
postStream.setProperties({
|
|
||||||
posts: [],
|
|
||||||
stream: [],
|
|
||||||
userFilters: [],
|
|
||||||
postIdentityMap: Ember.Map.create(),
|
|
||||||
summary: false,
|
|
||||||
loaded: false,
|
|
||||||
loadingAbove: false,
|
|
||||||
loadingBelow: false,
|
|
||||||
loadingFilter: false,
|
|
||||||
stagingPost: false
|
|
||||||
});
|
|
||||||
return postStream;
|
|
||||||
},
|
|
||||||
|
|
||||||
loadTopicView(topicId, args) {
|
|
||||||
const opts = _.merge({}, args);
|
|
||||||
let url = Discourse.getURL("/t/") + topicId;
|
|
||||||
if (opts.nearPost) {
|
|
||||||
url += "/" + opts.nearPost;
|
|
||||||
}
|
|
||||||
delete opts.nearPost;
|
|
||||||
delete opts.__type;
|
|
||||||
delete opts.store;
|
|
||||||
|
|
||||||
return PreloadStore.getAndRemove("topic_" + topicId, () => {
|
|
||||||
return Discourse.ajax(url + ".json", {data: opts});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default PostStream;
|
|
||||||
|
|
|
@ -319,11 +319,7 @@ const Topic = RestModel.extend({
|
||||||
keys.removeObject('details');
|
keys.removeObject('details');
|
||||||
keys.removeObject('post_stream');
|
keys.removeObject('post_stream');
|
||||||
|
|
||||||
const topic = this;
|
keys.forEach(key => this.set(key, json[key]));
|
||||||
keys.forEach(function (key) {
|
|
||||||
topic.set(key, json[key]);
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isPinnedUncategorized: function() {
|
isPinnedUncategorized: function() {
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import PostStream from "discourse/models/post-stream";
|
import { loadTopicView } from "discourse/models/post-stream";
|
||||||
|
|
||||||
export default Discourse.Route.extend({
|
export default Discourse.Route.extend({
|
||||||
model(params) {
|
model(params) {
|
||||||
const topic = this.store.createRecord("topic", { id: params.id });
|
const topic = this.store.createRecord("topic", { id: params.id });
|
||||||
return PostStream.loadTopicView(params.id).then(json => {
|
return loadTopicView(topic).then(() => topic);
|
||||||
topic.updateFromJson(json);
|
|
||||||
return topic;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
afterModel(topic) {
|
afterModel(topic) {
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<article class='placeholder'>
|
||||||
|
<div class='row'>
|
||||||
|
<div class="topic-avatar">
|
||||||
|
<div class='placeholder-avatar'></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='topic-body'>
|
||||||
|
<div class='placeholder-text'></div>
|
||||||
|
<div class='placeholder-text'></div>
|
||||||
|
<div class='placeholder-text'></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
|
@ -68,9 +68,8 @@
|
||||||
|
|
||||||
{{#unless model.postStream.loadingFilter}}
|
{{#unless model.postStream.loadingFilter}}
|
||||||
{{cloaked-collection itemViewClass="post"
|
{{cloaked-collection itemViewClass="post"
|
||||||
idProperty="post_number"
|
|
||||||
defaultHeight="200"
|
defaultHeight="200"
|
||||||
content=model.postStream.posts
|
content=model.postStream.postsWithPlaceholders
|
||||||
slackRatio="15"
|
slackRatio="15"
|
||||||
loadingHTML=""
|
loadingHTML=""
|
||||||
preservesContext="true"
|
preservesContext="true"
|
||||||
|
@ -78,8 +77,6 @@
|
||||||
offsetFixedTop="header"
|
offsetFixedTop="header"
|
||||||
offsetFixedBottom="#reply-control"}}
|
offsetFixedBottom="#reply-control"}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
{{conditional-loading-spinner condition=model.postStream.loadingBelow}}
|
|
||||||
</div>
|
</div>
|
||||||
<div id="topic-bottom"></div>
|
<div id="topic-bottom"></div>
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ const CloakedCollectionView = Ember.CollectionView.extend({
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super();
|
this._super();
|
||||||
|
|
||||||
if (idProperty) {
|
if (idProperty) {
|
||||||
this.set('elementId', cloakView + '-cloak-' + this.get('content.' + idProperty));
|
this.set('elementId', cloakView + '-cloak-' + this.get('content.' + idProperty));
|
||||||
}
|
}
|
||||||
|
@ -124,8 +123,9 @@ const CloakedCollectionView = Ember.CollectionView.extend({
|
||||||
const viewportTop = windowTop - slack,
|
const viewportTop = windowTop - slack,
|
||||||
topView = this.findTopView(childViews, viewportTop, 0, childViews.length-1);
|
topView = this.findTopView(childViews, viewportTop, 0, childViews.length-1);
|
||||||
|
|
||||||
let windowBottom = windowTop + windowHeight,
|
let windowBottom = windowTop + windowHeight;
|
||||||
viewportBottom = windowBottom + slack;
|
let viewportBottom = windowBottom + slack;
|
||||||
|
|
||||||
if (windowBottom > bodyHeight) { windowBottom = bodyHeight; }
|
if (windowBottom > bodyHeight) { windowBottom = bodyHeight; }
|
||||||
if (viewportBottom > bodyHeight) { viewportBottom = bodyHeight; }
|
if (viewportBottom > bodyHeight) { viewportBottom = bodyHeight; }
|
||||||
|
|
||||||
|
@ -139,22 +139,28 @@ const CloakedCollectionView = Ember.CollectionView.extend({
|
||||||
|
|
||||||
// Find the bottom view and what's onscreen
|
// Find the bottom view and what's onscreen
|
||||||
let bottomView = topView;
|
let bottomView = topView;
|
||||||
|
let bottomVisible = null;
|
||||||
while (bottomView < childViews.length) {
|
while (bottomView < childViews.length) {
|
||||||
const view = childViews[bottomView],
|
const view = childViews[bottomView];
|
||||||
$view = view.$();
|
const $view = view.$();
|
||||||
|
|
||||||
if (!$view) { break; }
|
if (!$view) { break; }
|
||||||
|
|
||||||
// in case of not full-window scrolling
|
// in case of not full-window scrolling
|
||||||
const scrollOffset = this.get('wrapperTop') || 0,
|
const scrollOffset = this.get('wrapperTop') || 0;
|
||||||
viewTop = $view.offset().top + scrollOffset,
|
const viewTop = $view.offset().top + scrollOffset;
|
||||||
viewBottom = viewTop + $view.height();
|
const viewBottom = viewTop + $view.height();
|
||||||
|
|
||||||
if (viewTop > viewportBottom) { break; }
|
if (viewTop > viewportBottom) { break; }
|
||||||
toUncloak.push(view);
|
toUncloak.push(view);
|
||||||
|
|
||||||
if (viewBottom > windowTop && viewTop <= windowBottom) {
|
if (viewBottom > windowTop && viewTop <= windowBottom) {
|
||||||
onscreen.push(view.get('content'));
|
const content = view.get('content');
|
||||||
|
onscreen.push(content);
|
||||||
|
|
||||||
|
if (!view.get('isPlaceholder')) {
|
||||||
|
bottomVisible = content;
|
||||||
|
}
|
||||||
onscreenCloaks.push(view);
|
onscreenCloaks.push(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +171,7 @@ const CloakedCollectionView = Ember.CollectionView.extend({
|
||||||
// If our controller has a `sawObjects` method, pass the on screen objects to it.
|
// If our controller has a `sawObjects` method, pass the on screen objects to it.
|
||||||
const controller = this.get('controller');
|
const controller = this.get('controller');
|
||||||
if (onscreen.length) {
|
if (onscreen.length) {
|
||||||
this.setProperties({topVisible: onscreen[0], bottomVisible: onscreen[onscreen.length-1]});
|
this.setProperties({topVisible: onscreen[0], bottomVisible });
|
||||||
if (controller && controller.sawObjects) {
|
if (controller && controller.sawObjects) {
|
||||||
Em.run.schedule('afterRender', function() {
|
Em.run.schedule('afterRender', function() {
|
||||||
controller.sawObjects(onscreen);
|
controller.sawObjects(onscreen);
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
|
export function Placeholder(viewName) {
|
||||||
|
this.viewName = viewName;
|
||||||
|
}
|
||||||
|
|
||||||
export default Ember.View.extend({
|
export default Ember.View.extend({
|
||||||
attributeBindings: ['style'],
|
attributeBindings: ['style'],
|
||||||
_containedView: null,
|
_containedView: null,
|
||||||
_scheduled: null,
|
_scheduled: null,
|
||||||
|
isPlaceholder: null,
|
||||||
|
|
||||||
init: function() {
|
init() {
|
||||||
this._super();
|
this._super();
|
||||||
this._scheduled = false;
|
this._scheduled = false;
|
||||||
this._childViews = [];
|
this._childViews = [];
|
||||||
|
@ -15,6 +20,8 @@ export default Ember.View.extend({
|
||||||
this._childViews[0] = cv;
|
this._childViews[0] = cv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.set('isPlaceholder', cv && (cv.get('content') instanceof Placeholder));
|
||||||
|
|
||||||
if (cv) {
|
if (cv) {
|
||||||
cv.set('_parentView', this);
|
cv.set('_parentView', this);
|
||||||
cv.set('templateData', this.get('templateData'));
|
cv.set('templateData', this.get('templateData'));
|
||||||
|
@ -56,8 +63,8 @@ export default Ember.View.extend({
|
||||||
if (state !== 'inDOM' && state !== 'preRender') { return; }
|
if (state !== 'inDOM' && state !== 'preRender') { return; }
|
||||||
|
|
||||||
if (!this._containedView) {
|
if (!this._containedView) {
|
||||||
const model = this.get('content'),
|
const model = this.get('content');
|
||||||
container = this.get('container');
|
const container = this.get('container');
|
||||||
|
|
||||||
let controller;
|
let controller;
|
||||||
|
|
||||||
|
@ -80,8 +87,8 @@ export default Ember.View.extend({
|
||||||
controller = factory.create({ model, parentController, target: parentController });
|
controller = factory.create({ model, parentController, target: parentController });
|
||||||
}
|
}
|
||||||
|
|
||||||
const createArgs = {},
|
const createArgs = {};
|
||||||
target = controller || model;
|
const target = controller || model;
|
||||||
|
|
||||||
if (this.get('preservesContext')) {
|
if (this.get('preservesContext')) {
|
||||||
createArgs.content = target;
|
createArgs.content = target;
|
||||||
|
@ -89,12 +96,10 @@ export default Ember.View.extend({
|
||||||
createArgs.context = target;
|
createArgs.context = target;
|
||||||
}
|
}
|
||||||
if (controller) { createArgs.controller = controller; }
|
if (controller) { createArgs.controller = controller; }
|
||||||
this.setProperties({
|
this.setProperties({ style: null, loading: false });
|
||||||
style: null,
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setContainedView(this.createChildView(this.get('cloaks'), createArgs));
|
const cloaks = target && (target instanceof Placeholder) ? target.viewName : this.get('cloaks');
|
||||||
|
this.setContainedView(this.createChildView(cloaks, createArgs));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -107,7 +112,7 @@ export default Ember.View.extend({
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
if (this._containedView && (this._state || this.state) === 'inDOM') {
|
if (this._containedView && (this._state || this.state) === 'inDOM') {
|
||||||
const style = 'height: ' + this.$().height() + 'px;';
|
const style = `height: ${this.$().height()}px;`.htmlSafe();
|
||||||
this.set('style', style);
|
this.set('style', style);
|
||||||
this.$().prop('style', style);
|
this.$().prop('style', style);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export default Ember.View.extend({ templateName: 'post-placeholder' });
|
|
@ -1,3 +1,19 @@
|
||||||
|
.placeholder-avatar {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: dark-light-diff($primary, $secondary, 90%, -75%);
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-text {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: dark-light-diff($primary, $secondary, 90%, -75%);
|
||||||
|
width: 100%;
|
||||||
|
height: 1.5em;
|
||||||
|
margin-bottom: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
.names {
|
.names {
|
||||||
float: left;
|
float: left;
|
||||||
.username {
|
.username {
|
||||||
|
|
|
@ -685,6 +685,7 @@ blockquote {
|
||||||
$topic-body-width: 690px;
|
$topic-body-width: 690px;
|
||||||
$topic-body-width-padding: 11px;
|
$topic-body-width-padding: 11px;
|
||||||
$topic-avatar-width: 45px;
|
$topic-avatar-width: 45px;
|
||||||
|
|
||||||
.topic-body {
|
.topic-body {
|
||||||
width: $topic-body-width;
|
width: $topic-body-width;
|
||||||
float: left;
|
float: left;
|
||||||
|
|
|
@ -65,6 +65,7 @@ test('appending posts', function() {
|
||||||
const postStream = buildStream(4567, [1, 3, 4]);
|
const postStream = buildStream(4567, [1, 3, 4]);
|
||||||
const store = postStream.store;
|
const store = postStream.store;
|
||||||
|
|
||||||
|
equal(postStream.get('firstPostId'), 1);
|
||||||
equal(postStream.get('lastPostId'), 4, "the last post id is 4");
|
equal(postStream.get('lastPostId'), 4, "the last post id is 4");
|
||||||
|
|
||||||
ok(!postStream.get('hasPosts'), "there are no posts by default");
|
ok(!postStream.get('hasPosts'), "there are no posts by default");
|
||||||
|
@ -283,23 +284,26 @@ test("storePost", function() {
|
||||||
const postWithoutId = store.createRecord('post', {raw: 'hello world'});
|
const postWithoutId = store.createRecord('post', {raw: 'hello world'});
|
||||||
stored = postStream.storePost(postWithoutId);
|
stored = postStream.storePost(postWithoutId);
|
||||||
equal(stored, postWithoutId, "it returns the same post back");
|
equal(stored, postWithoutId, "it returns the same post back");
|
||||||
equal(postStream.get('postIdentityMap.size'), 1, "it does not add a new entry into the identity map");
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("identity map", function() {
|
test("identity map", function() {
|
||||||
const postStream = buildStream(1234),
|
const postStream = buildStream(1234);
|
||||||
store = postStream.store;
|
const store = postStream.store;
|
||||||
|
|
||||||
const p1 = postStream.appendPost(store.createRecord('post', {id: 1, post_number: 1}));
|
const p1 = postStream.appendPost(store.createRecord('post', {id: 1, post_number: 1}));
|
||||||
postStream.appendPost(store.createRecord('post', {id: 3, post_number: 4}));
|
const p3 = postStream.appendPost(store.createRecord('post', {id: 3, post_number: 4}));
|
||||||
|
|
||||||
equal(postStream.findLoadedPost(1), p1, "it can return cached posts by id");
|
equal(postStream.findLoadedPost(1), p1, "it can return cached posts by id");
|
||||||
blank(postStream.findLoadedPost(4), "it can't find uncached posts");
|
blank(postStream.findLoadedPost(4), "it can't find uncached posts");
|
||||||
|
|
||||||
deepEqual(postStream.listUnloadedIds([10, 11, 12]), [10, 11, 12], "it returns a list of all unloaded ids");
|
// Find posts by ids uses the identity map
|
||||||
blank(postStream.listUnloadedIds([1, 3]), "if we have loaded all posts it's blank");
|
postStream.findPostsByIds([1, 2, 3]).then(result => {
|
||||||
deepEqual(postStream.listUnloadedIds([1, 2, 3, 4]), [2, 4], "it only returns unloaded posts");
|
equal(result.length, 3);
|
||||||
|
equal(result.objectAt(0), p1);
|
||||||
|
equal(result.objectAt(1).get('post_number'), 2);
|
||||||
|
equal(result.objectAt(2), p3);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("loadIntoIdentityMap with no data", () => {
|
test("loadIntoIdentityMap with no data", () => {
|
||||||
|
@ -439,7 +443,6 @@ test('triggerNewPostInStream', function() {
|
||||||
ok(postStream.appendMore.calledOnce, "delegates to appendMore because the last post is loaded");
|
ok(postStream.appendMore.calledOnce, "delegates to appendMore because the last post is loaded");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("loadedAllPosts when the id changes", function() {
|
test("loadedAllPosts when the id changes", function() {
|
||||||
// This can happen in a race condition between staging a post and it coming through on the
|
// This can happen in a race condition between staging a post and it coming through on the
|
||||||
// message bus. If the id of a post changes we should reconsider the loadedAllPosts property.
|
// message bus. If the id of a post changes we should reconsider the loadedAllPosts property.
|
||||||
|
@ -475,3 +478,45 @@ test("comitting and triggerNewPostInStream race condition", function() {
|
||||||
equal(postStream.get('filteredPostsCount'), 1, "it does not add the same post twice");
|
equal(postStream.get('filteredPostsCount'), 1, "it does not add the same post twice");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("postsWithPlaceholders", () => {
|
||||||
|
const postStream = buildStream(4964, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||||
|
const postsWithPlaceholders = postStream.get('postsWithPlaceholders');
|
||||||
|
const store = postStream.store;
|
||||||
|
|
||||||
|
const testProxy = Ember.ArrayProxy.create({ content: postsWithPlaceholders });
|
||||||
|
|
||||||
|
const p1 = store.createRecord('post', {id: 1, post_number: 1});
|
||||||
|
const p2 = store.createRecord('post', {id: 2, post_number: 2});
|
||||||
|
const p3 = store.createRecord('post', {id: 3, post_number: 3});
|
||||||
|
const p4 = store.createRecord('post', {id: 4, post_number: 4});
|
||||||
|
|
||||||
|
postStream.appendPost(p1);
|
||||||
|
postStream.appendPost(p2);
|
||||||
|
postStream.appendPost(p3);
|
||||||
|
|
||||||
|
// Test enumerable and array access
|
||||||
|
equal(postsWithPlaceholders.get('length'), 3);
|
||||||
|
equal(testProxy.get('length'), 3);
|
||||||
|
equal(postsWithPlaceholders.nextObject(0), p1);
|
||||||
|
equal(postsWithPlaceholders.objectAt(0), p1);
|
||||||
|
equal(postsWithPlaceholders.nextObject(1, p1), p2);
|
||||||
|
equal(postsWithPlaceholders.objectAt(1), p2);
|
||||||
|
equal(postsWithPlaceholders.nextObject(2, p2), p3);
|
||||||
|
equal(postsWithPlaceholders.objectAt(2), p3);
|
||||||
|
|
||||||
|
const promise = postStream.appendMore();
|
||||||
|
equal(postsWithPlaceholders.get('length'), 8, 'we immediately have a larger placeholder window');
|
||||||
|
equal(testProxy.get('length'), 8);
|
||||||
|
ok(!!postsWithPlaceholders.nextObject(3, p3));
|
||||||
|
ok(!!postsWithPlaceholders.objectAt(4));
|
||||||
|
ok(postsWithPlaceholders.objectAt(3) !== p4);
|
||||||
|
ok(testProxy.objectAt(3) !== p4);
|
||||||
|
|
||||||
|
return promise.then(() => {
|
||||||
|
equal(postsWithPlaceholders.objectAt(3), p4);
|
||||||
|
equal(postsWithPlaceholders.get('length'), 8, 'have a larger placeholder window when loaded');
|
||||||
|
equal(testProxy.get('length'), 8);
|
||||||
|
equal(testProxy.objectAt(3), p4);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue