We can use DI for this, which makes it easier for plugins to subscribe to the message bus.
716 lines
21 KiB
JavaScript
716 lines
21 KiB
JavaScript
import ObjectController from 'discourse/controllers/object';
|
|
import BufferedContent from 'discourse/mixins/buffered-content';
|
|
import { spinnerHTML } from 'discourse/helpers/loading-spinner';
|
|
import Topic from 'discourse/models/topic';
|
|
|
|
export default ObjectController.extend(Discourse.SelectedPostsCount, BufferedContent, {
|
|
multiSelect: false,
|
|
needs: ['header', 'modal', 'composer', 'quote-button', 'search', 'topic-progress', 'application'],
|
|
allPostsSelected: false,
|
|
editingTopic: false,
|
|
selectedPosts: null,
|
|
selectedReplies: null,
|
|
queryParams: ['filter', 'username_filters', 'show_deleted'],
|
|
searchHighlight: null,
|
|
|
|
maxTitleLength: Discourse.computed.setting('max_topic_title_length'),
|
|
|
|
contextChanged: function() {
|
|
this.set('controllers.search.searchContext', this.get('model.searchContext'));
|
|
}.observes('topic'),
|
|
|
|
_titleChanged: function() {
|
|
var title = this.get('title');
|
|
if (!Ember.isEmpty(title)) {
|
|
|
|
// Note normally you don't have to trigger this, but topic titles can be updated
|
|
// and are sometimes lazily loaded.
|
|
this.send('refreshTitle');
|
|
}
|
|
}.observes('title', 'category'),
|
|
|
|
termChanged: function() {
|
|
var dropdown = this.get('controllers.header.visibleDropdown');
|
|
var term = this.get('controllers.search.term');
|
|
|
|
if(dropdown === 'search-dropdown' && term){
|
|
this.set('searchHighlight', term);
|
|
} else {
|
|
if(this.get('searchHighlight')){
|
|
this.set('searchHighlight', null);
|
|
}
|
|
}
|
|
|
|
}.observes('controllers.search.term', 'controllers.header.visibleDropdown'),
|
|
|
|
postStreamLoadedAllPostsChanged: function(){
|
|
// semantics of loaded all posts are slightly diff at topic level,
|
|
// it just means that we "once" loaded all posts, this means we don't
|
|
// keep re-rendering the suggested topics when new posts zoom in
|
|
var loaded = this.get('postStream.loadedAllPosts');
|
|
|
|
if(loaded) {
|
|
this.set('loadedTopicId', this.get('model.id'));
|
|
} else {
|
|
loaded = this.get('loadedTopicId') === this.get('model.id');
|
|
}
|
|
|
|
this.set('loadedAllPosts', loaded);
|
|
|
|
}.observes('postStream', 'postStream.loadedAllPosts'),
|
|
|
|
show_deleted: function(key, value) {
|
|
var postStream = this.get('postStream');
|
|
if (!postStream) { return; }
|
|
|
|
if (arguments.length > 1) {
|
|
postStream.set('show_deleted', value);
|
|
}
|
|
return postStream.get('show_deleted') ? true : undefined;
|
|
}.property('postStream.summary'),
|
|
|
|
filter: function(key, value) {
|
|
var postStream = this.get('postStream');
|
|
if (!postStream) { return; }
|
|
|
|
if (arguments.length > 1) {
|
|
postStream.set('summary', value === "summary");
|
|
}
|
|
return postStream.get('summary') ? "summary" : undefined;
|
|
}.property('postStream.summary'),
|
|
|
|
username_filters: function(key, value) {
|
|
var postStream = this.get('postStream');
|
|
if (!postStream) { return; }
|
|
|
|
if (arguments.length > 1) {
|
|
postStream.set('streamFilters.username_filters', value);
|
|
}
|
|
return postStream.get('streamFilters.username_filters');
|
|
}.property('postStream.streamFilters.username_filters'),
|
|
|
|
init: function() {
|
|
this._super();
|
|
this.set('selectedPosts', []);
|
|
this.set('selectedReplies', []);
|
|
},
|
|
|
|
actions: {
|
|
deleteTopic: function() {
|
|
this.deleteTopic();
|
|
},
|
|
|
|
// Post related methods
|
|
replyToPost: function(post) {
|
|
var composerController = this.get('controllers.composer'),
|
|
quoteController = this.get('controllers.quote-button'),
|
|
quotedText = Discourse.Quote.build(quoteController.get('post'), quoteController.get('buffer')),
|
|
topic = post ? post.get('topic') : this.get('model');
|
|
|
|
quoteController.set('buffer', '');
|
|
|
|
if (composerController.get('content.topic.id') === topic.get('id') &&
|
|
composerController.get('content.action') === Discourse.Composer.REPLY) {
|
|
composerController.set('content.post', post);
|
|
composerController.set('content.composeState', Discourse.Composer.OPEN);
|
|
composerController.appendText(quotedText);
|
|
} else {
|
|
|
|
var opts = {
|
|
action: Discourse.Composer.REPLY,
|
|
draftKey: topic.get('draft_key'),
|
|
draftSequence: topic.get('draft_sequence')
|
|
};
|
|
|
|
if(post && post.get("post_number") !== 1){
|
|
opts.post = post;
|
|
} else {
|
|
opts.topic = topic;
|
|
}
|
|
|
|
composerController.open(opts).then(function() {
|
|
composerController.appendText(quotedText);
|
|
});
|
|
}
|
|
return false;
|
|
},
|
|
|
|
toggleLike: function(post) {
|
|
var likeAction = post.get('actionByName.like');
|
|
if (likeAction && likeAction.get('canToggle')) {
|
|
likeAction.toggle();
|
|
}
|
|
},
|
|
|
|
recoverPost: function(post) {
|
|
// Recovering the first post recovers the topic instead
|
|
if (post.get('post_number') === 1) {
|
|
this.recoverTopic();
|
|
return;
|
|
}
|
|
post.recover();
|
|
},
|
|
|
|
deletePost: function(post) {
|
|
|
|
// Deleting the first post deletes the topic
|
|
if (post.get('post_number') === 1) {
|
|
this.deleteTopic();
|
|
return;
|
|
}
|
|
|
|
var user = Discourse.User.current(),
|
|
replyCount = post.get('reply_count'),
|
|
self = this;
|
|
|
|
// If the user is staff and the post has replies, ask if they want to delete replies too.
|
|
if (user.get('staff') && replyCount > 0) {
|
|
bootbox.dialog(I18n.t("post.controls.delete_replies.confirm", {count: replyCount}), [
|
|
{label: I18n.t("cancel"),
|
|
'class': 'btn-danger rightg'},
|
|
{label: I18n.t("post.controls.delete_replies.no_value"),
|
|
callback: function() {
|
|
post.destroy(user);
|
|
}
|
|
},
|
|
{label: I18n.t("post.controls.delete_replies.yes_value"),
|
|
'class': 'btn-primary',
|
|
callback: function() {
|
|
Discourse.Post.deleteMany([post], [post]);
|
|
self.get('postStream.posts').forEach(function (p) {
|
|
if (p === post || p.get('reply_to_post_number') === post.get('post_number')) {
|
|
p.setDeletedState(user);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
]);
|
|
} else {
|
|
post.destroy(user).then(null, function(e) {
|
|
post.undoDeleteState();
|
|
var response = $.parseJSON(e.responseText);
|
|
if (response && response.errors) {
|
|
bootbox.alert(response.errors[0]);
|
|
} else {
|
|
bootbox.alert(I18n.t('generic_error'));
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
editPost: function(post) {
|
|
if (!Discourse.User.current()) {
|
|
return bootbox.alert(I18n.t('post.controls.edit_anonymous'));
|
|
}
|
|
|
|
this.get('controllers.composer').open({
|
|
post: post,
|
|
action: Discourse.Composer.EDIT,
|
|
draftKey: post.get('topic.draft_key'),
|
|
draftSequence: post.get('topic.draft_sequence')
|
|
});
|
|
},
|
|
|
|
toggleBookmark: function(post) {
|
|
if (!Discourse.User.current()) {
|
|
alert(I18n.t("bookmarks.not_bookmarked"));
|
|
return;
|
|
}
|
|
if (post) {
|
|
return post.toggleBookmark().catch(function(error) {
|
|
if (error && error.responseText) {
|
|
bootbox.alert($.parseJSON(error.responseText).errors[0]);
|
|
} else {
|
|
bootbox.alert(I18n.t('generic_error'));
|
|
}
|
|
});
|
|
} else {
|
|
return this.get("model").toggleBookmark();
|
|
}
|
|
},
|
|
|
|
jumpTop: function() {
|
|
this.get('controllers.topic-progress').send('jumpTop');
|
|
},
|
|
|
|
selectAll: function() {
|
|
var posts = this.get('postStream.posts'),
|
|
selectedPosts = this.get('selectedPosts');
|
|
if (posts) {
|
|
selectedPosts.addObjects(posts);
|
|
}
|
|
this.set('allPostsSelected', true);
|
|
},
|
|
|
|
deselectAll: function() {
|
|
this.get('selectedPosts').clear();
|
|
this.get('selectedReplies').clear();
|
|
this.set('allPostsSelected', false);
|
|
},
|
|
|
|
toggleParticipant: function(user) {
|
|
this.get('postStream').toggleParticipant(Em.get(user, 'username'));
|
|
},
|
|
|
|
editTopic: function() {
|
|
if (!this.get('details.can_edit')) return false;
|
|
|
|
this.set('editingTopic', true);
|
|
return false;
|
|
},
|
|
|
|
cancelEditingTopic: function() {
|
|
this.set('editingTopic', false);
|
|
this.rollbackBuffer();
|
|
},
|
|
|
|
toggleMultiSelect: function() {
|
|
this.toggleProperty('multiSelect');
|
|
},
|
|
|
|
finishedEditingTopic: function() {
|
|
if (!this.get('editingTopic')) { return; }
|
|
|
|
// save the modifications
|
|
var self = this,
|
|
props = this.get('buffered.buffer');
|
|
|
|
Topic.update(this.get('model'), props).then(function() {
|
|
// Note we roll back on success here because `update` saves
|
|
// the properties to the topic.
|
|
self.rollbackBuffer();
|
|
self.set('editingTopic', false);
|
|
}).catch(function(error) {
|
|
if (error && error.responseText) {
|
|
bootbox.alert($.parseJSON(error.responseText).errors[0]);
|
|
} else {
|
|
bootbox.alert(I18n.t('generic_error'));
|
|
}
|
|
});
|
|
},
|
|
|
|
toggledSelectedPost: function(post) {
|
|
this.performTogglePost(post);
|
|
},
|
|
|
|
toggledSelectedPostReplies: function(post) {
|
|
var selectedReplies = this.get('selectedReplies');
|
|
if (this.performTogglePost(post)) {
|
|
selectedReplies.addObject(post);
|
|
} else {
|
|
selectedReplies.removeObject(post);
|
|
}
|
|
},
|
|
|
|
deleteSelected: function() {
|
|
var self = this;
|
|
bootbox.confirm(I18n.t("post.delete.confirm", { count: this.get('selectedPostsCount')}), function(result) {
|
|
if (result) {
|
|
|
|
// If all posts are selected, it's the same thing as deleting the topic
|
|
if (self.get('allPostsSelected')) {
|
|
return self.deleteTopic();
|
|
}
|
|
|
|
var selectedPosts = self.get('selectedPosts'),
|
|
selectedReplies = self.get('selectedReplies'),
|
|
postStream = self.get('postStream'),
|
|
toRemove = [];
|
|
|
|
Discourse.Post.deleteMany(selectedPosts, selectedReplies);
|
|
postStream.get('posts').forEach(function (p) {
|
|
if (self.postSelected(p)) { toRemove.addObject(p); }
|
|
});
|
|
|
|
postStream.removePosts(toRemove);
|
|
self.send('toggleMultiSelect');
|
|
}
|
|
});
|
|
},
|
|
|
|
expandHidden: function(post) {
|
|
post.expandHidden();
|
|
},
|
|
|
|
toggleVisibility: function() {
|
|
this.get('content').toggleStatus('visible');
|
|
},
|
|
|
|
toggleClosed: function() {
|
|
this.get('content').toggleStatus('closed');
|
|
},
|
|
|
|
recoverTopic: function() {
|
|
this.get('content').recover();
|
|
},
|
|
|
|
makeBanner: function() {
|
|
this.get('content').makeBanner();
|
|
},
|
|
|
|
removeBanner: function() {
|
|
this.get('content').removeBanner();
|
|
},
|
|
|
|
togglePinned: function() {
|
|
// Note that this is different than clearPin
|
|
this.get('content').setStatus('pinned', this.get('pinned_at') ? false : true);
|
|
},
|
|
|
|
togglePinnedGlobally: function() {
|
|
// Note that this is different than clearPin
|
|
this.get('content').setStatus('pinned_globally', this.get('pinned_at') ? false : true);
|
|
},
|
|
|
|
toggleArchived: function() {
|
|
this.get('content').toggleStatus('archived');
|
|
},
|
|
|
|
// Toggle the star on the topic
|
|
toggleStar: function() {
|
|
this.get('content').toggleStar();
|
|
},
|
|
|
|
/**
|
|
Clears the pin from a topic for the currently logged in user
|
|
|
|
@method clearPin
|
|
**/
|
|
clearPin: function() {
|
|
this.get('content').clearPin();
|
|
},
|
|
|
|
togglePinnedForUser: function() {
|
|
if (this.get('pinned_at')) {
|
|
if (this.get('pinned')) {
|
|
this.get('content').clearPin();
|
|
} else {
|
|
this.get('content').rePin();
|
|
}
|
|
}
|
|
},
|
|
|
|
replyAsNewTopic: function(post) {
|
|
var composerController = this.get('controllers.composer'),
|
|
quoteController = this.get('controllers.quote-button'),
|
|
quotedText = Discourse.Quote.build(quoteController.get('post'), quoteController.get('buffer')),
|
|
self = this;
|
|
|
|
quoteController.deselectText();
|
|
|
|
composerController.open({
|
|
action: Discourse.Composer.CREATE_TOPIC,
|
|
draftKey: Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY
|
|
}).then(function() {
|
|
return Em.isEmpty(quotedText) ? Discourse.Post.loadQuote(post.get('id')) : quotedText;
|
|
}).then(function(q) {
|
|
var postUrl = "" + location.protocol + "//" + location.host + (post.get('url')),
|
|
postLink = "[" + self.get('title') + "](" + postUrl + ")";
|
|
composerController.appendText(I18n.t("post.continue_discussion", { postLink: postLink }) + "\n\n" + q);
|
|
});
|
|
},
|
|
|
|
expandFirstPost: function(post) {
|
|
var self = this;
|
|
this.set('loadingExpanded', true);
|
|
post.expand().then(function() {
|
|
self.set('firstPostExpanded', true);
|
|
}).catch(function(error) {
|
|
bootbox.alert($.parseJSON(error.responseText).errors);
|
|
}).finally(function() {
|
|
self.set('loadingExpanded', false);
|
|
});
|
|
},
|
|
|
|
retryLoading: function() {
|
|
var self = this;
|
|
self.set('retrying', true);
|
|
this.get('postStream').refresh().then(function() {
|
|
self.set('retrying', false);
|
|
}, function() {
|
|
self.set('retrying', false);
|
|
});
|
|
},
|
|
|
|
toggleWiki: function(post) {
|
|
// the request to the server is made in an observer in the post class
|
|
post.toggleProperty('wiki');
|
|
},
|
|
|
|
togglePostType(post) {
|
|
// the request to the server is made in an observer in the post class
|
|
const regular = this.site.get('post_types.regular'),
|
|
moderator = this.site.get('post_types.moderator_action');
|
|
|
|
if (post.get("post_type") === moderator) {
|
|
post.set("post_type", regular);
|
|
} else {
|
|
post.set("post_type", moderator);
|
|
}
|
|
},
|
|
|
|
rebakePost: function (post) {
|
|
post.rebake();
|
|
},
|
|
|
|
unhidePost: function (post) {
|
|
post.unhide();
|
|
}
|
|
},
|
|
|
|
togglePinnedState: function() {
|
|
this.send('togglePinnedForUser');
|
|
},
|
|
|
|
showExpandButton: function() {
|
|
var post = this.get('post');
|
|
return post.get('post_number') === 1 && post.get('topic.expandable_first_post');
|
|
}.property(),
|
|
|
|
canMergeTopic: function() {
|
|
if (!this.get('details.can_move_posts')) return false;
|
|
return (this.get('selectedPostsCount') > 0);
|
|
}.property('selectedPostsCount'),
|
|
|
|
canSplitTopic: function() {
|
|
if (!this.get('details.can_move_posts')) return false;
|
|
if (this.get('allPostsSelected')) return false;
|
|
return (this.get('selectedPostsCount') > 0);
|
|
}.property('selectedPostsCount'),
|
|
|
|
canChangeOwner: function() {
|
|
if (!Discourse.User.current() || !Discourse.User.current().admin) return false;
|
|
return !!this.get('selectedPostsUsername');
|
|
}.property('selectedPostsUsername'),
|
|
|
|
categories: function() {
|
|
return Discourse.Category.list();
|
|
}.property(),
|
|
|
|
canSelectAll: Em.computed.not('allPostsSelected'),
|
|
|
|
canDeselectAll: function () {
|
|
if (this.get('selectedPostsCount') > 0) return true;
|
|
if (this.get('allPostsSelected')) return true;
|
|
}.property('selectedPostsCount', 'allPostsSelected'),
|
|
|
|
canDeleteSelected: function() {
|
|
var selectedPosts = this.get('selectedPosts');
|
|
|
|
if (this.get('allPostsSelected')) return true;
|
|
if (this.get('selectedPostsCount') === 0) return false;
|
|
|
|
var canDelete = true;
|
|
selectedPosts.forEach(function(p) {
|
|
if (!p.get('can_delete')) {
|
|
canDelete = false;
|
|
return false;
|
|
}
|
|
});
|
|
return canDelete;
|
|
}.property('selectedPostsCount'),
|
|
|
|
hasError: Ember.computed.or('notFoundHtml', 'message'),
|
|
noErrorYet: Ember.computed.not('hasError'),
|
|
|
|
multiSelectChanged: function() {
|
|
// Deselect all posts when multi select is turned off
|
|
if (!this.get('multiSelect')) {
|
|
this.send('deselectAll');
|
|
}
|
|
}.observes('multiSelect'),
|
|
|
|
deselectPost: function(post) {
|
|
this.get('selectedPosts').removeObject(post);
|
|
|
|
var selectedReplies = this.get('selectedReplies');
|
|
selectedReplies.removeObject(post);
|
|
|
|
var selectedReply = selectedReplies.findProperty('post_number', post.get('reply_to_post_number'));
|
|
if (selectedReply) { selectedReplies.removeObject(selectedReply); }
|
|
|
|
this.set('allPostsSelected', false);
|
|
},
|
|
|
|
postSelected: function(post) {
|
|
if (this.get('allPostsSelected')) { return true; }
|
|
if (this.get('selectedPosts').contains(post)) { return true; }
|
|
if (this.get('selectedReplies').findProperty('post_number', post.get('reply_to_post_number'))) { return true; }
|
|
|
|
return false;
|
|
},
|
|
|
|
showStarButton: function() {
|
|
return Discourse.User.current() && !this.get('isPrivateMessage');
|
|
}.property('isPrivateMessage'),
|
|
|
|
loadingHTML: function() {
|
|
return spinnerHTML;
|
|
}.property(),
|
|
|
|
recoverTopic: function() {
|
|
this.get('content').recover();
|
|
},
|
|
|
|
deleteTopic: function() {
|
|
this.unsubscribe();
|
|
this.get('content').destroy(Discourse.User.current());
|
|
},
|
|
|
|
// Receive notifications for this topic
|
|
subscribe() {
|
|
// Unsubscribe before subscribing again
|
|
this.unsubscribe();
|
|
|
|
const self = this;
|
|
this.messageBus.subscribe("/topic/" + this.get('id'), function(data) {
|
|
const topic = self.get('model');
|
|
|
|
if (data.notification_level_change) {
|
|
topic.set('details.notification_level', data.notification_level_change);
|
|
topic.set('details.notifications_reason_id', data.notifications_reason_id);
|
|
return;
|
|
}
|
|
|
|
const postStream = self.get('postStream');
|
|
switch (data.type) {
|
|
case "revised":
|
|
case "acted":
|
|
case "rebaked": {
|
|
// TODO we could update less data for "acted" (only post actions)
|
|
postStream.triggerChangedPost(data.id, data.updated_at);
|
|
return;
|
|
}
|
|
case "deleted": {
|
|
postStream.triggerDeletedPost(data.id, data.post_number);
|
|
return;
|
|
}
|
|
case "recovered": {
|
|
postStream.triggerRecoveredPost(data.id, data.post_number);
|
|
return;
|
|
}
|
|
case "created": {
|
|
postStream.triggerNewPostInStream(data.id);
|
|
return;
|
|
}
|
|
default: {
|
|
Em.Logger.warn("unknown topic bus message type", data);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
unsubscribe: function() {
|
|
var topicId = this.get('content.id');
|
|
if (!topicId) return;
|
|
|
|
// there is a condition where the view never calls unsubscribe, navigate to a topic from a topic
|
|
this.messageBus.unsubscribe('/topic/*');
|
|
},
|
|
|
|
// Topic related
|
|
reply: function() {
|
|
this.replyToPost();
|
|
},
|
|
|
|
performTogglePost: function(post) {
|
|
var selectedPosts = this.get('selectedPosts');
|
|
if (this.postSelected(post)) {
|
|
this.deselectPost(post);
|
|
return false;
|
|
} else {
|
|
selectedPosts.addObject(post);
|
|
|
|
// If the user manually selects all posts, all posts are selected
|
|
if (selectedPosts.length === this.get('posts_count')) {
|
|
this.set('allPostsSelected', true);
|
|
}
|
|
return true;
|
|
}
|
|
},
|
|
|
|
// If our current post is changed, notify the router
|
|
_currentPostChanged: function() {
|
|
var currentPost = this.get('currentPost');
|
|
if (currentPost) {
|
|
this.send('postChangedRoute', currentPost);
|
|
}
|
|
}.observes('currentPost'),
|
|
|
|
readPosts: function(topicId, postNumbers) {
|
|
var postStream = this.get('postStream');
|
|
|
|
if(this.get('postStream.topic.id') === topicId){
|
|
_.each(postStream.get('posts'), function(post){
|
|
// optimise heavy loop
|
|
// TODO identity map for postNumber
|
|
if(_.include(postNumbers,post.post_number) && !post.read){
|
|
post.set("read", true);
|
|
}
|
|
});
|
|
|
|
var max = _.max(postNumbers);
|
|
if(max > this.get('last_read_post_number')){
|
|
this.set('last_read_post_number', max);
|
|
}
|
|
}
|
|
},
|
|
|
|
// Called the the topmost visible post on the page changes.
|
|
topVisibleChanged: function(post) {
|
|
if (!post) { return; }
|
|
|
|
var postStream = this.get('postStream'),
|
|
firstLoadedPost = postStream.get('firstLoadedPost');
|
|
|
|
this.set('currentPost', post.get('post_number'));
|
|
|
|
if (post.get('post_number') === 1) { return; }
|
|
|
|
if (firstLoadedPost && firstLoadedPost === post) {
|
|
// Note: jQuery shouldn't be done in a controller, but how else can we
|
|
// trigger a scroll after a promise resolves in a controller? We need
|
|
// to do this to preserve upwards infinte scrolling.
|
|
var $body = $('body'),
|
|
$elem = $('#post-cloak-' + post.get('post_number')),
|
|
distToElement = $body.scrollTop() - $elem.position().top;
|
|
|
|
postStream.prependMore().then(function() {
|
|
Em.run.next(function () {
|
|
$elem = $('#post-cloak-' + post.get('post_number'));
|
|
|
|
// Quickly going back might mean the element is destroyed
|
|
var position = $elem.position();
|
|
if (position && position.top) {
|
|
$('html, body').scrollTop(position.top + distToElement);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
Called the the bottommost visible post on the page changes.
|
|
|
|
@method bottomVisibleChanged
|
|
@params {Discourse.Post} post that is at the bottom
|
|
**/
|
|
bottomVisibleChanged: function(post) {
|
|
if (!post) { return; }
|
|
|
|
var postStream = this.get('postStream'),
|
|
lastLoadedPost = postStream.get('lastLoadedPost');
|
|
|
|
this.set('controllers.topic-progress.progressPosition', postStream.progressIndexOfPost(post));
|
|
|
|
if (lastLoadedPost && lastLoadedPost === post) {
|
|
postStream.appendMore();
|
|
}
|
|
},
|
|
|
|
_showFooter: function() {
|
|
this.set("controllers.application.showFooter", this.get("postStream.loadedAllPosts"));
|
|
}.observes("postStream.loadedAllPosts")
|
|
|
|
});
|