mirror of
https://github.com/discourse/discourse.git
synced 2025-02-11 06:58:46 +08:00
728 lines
21 KiB
JavaScript
728 lines
21 KiB
JavaScript
import BufferedContent from 'discourse/mixins/buffered-content';
|
|
import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
|
|
import { spinnerHTML } from 'discourse/helpers/loading-spinner';
|
|
import Topic from 'discourse/models/topic';
|
|
import Quote from 'discourse/lib/quote';
|
|
import { setting } from 'discourse/lib/computed';
|
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
|
import computed from 'ember-addons/ember-computed-decorators';
|
|
|
|
export default Ember.Controller.extend(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,
|
|
loadedAllPosts: false,
|
|
enteredAt: null,
|
|
firstPostExpanded: false,
|
|
retrying: false,
|
|
|
|
maxTitleLength: setting('max_topic_title_length'),
|
|
|
|
contextChanged: function() {
|
|
this.set('controllers.search.searchContext', this.get('model.searchContext'));
|
|
}.observes('topic'),
|
|
|
|
_titleChanged: function() {
|
|
const title = this.get('model.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('model.title', 'category'),
|
|
|
|
termChanged: function() {
|
|
const dropdown = this.get('controllers.header.visibleDropdown');
|
|
const 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
|
|
let loaded = this.get('model.postStream.loadedAllPosts');
|
|
|
|
if (loaded) {
|
|
this.set('model.loadedTopicId', this.get('model.id'));
|
|
} else {
|
|
loaded = this.get('model.loadedTopicId') === this.get('model.id');
|
|
}
|
|
|
|
this.set('loadedAllPosts', loaded);
|
|
|
|
}.observes('model.postStream', 'model.postStream.loadedAllPosts'),
|
|
|
|
@computed('model.postStream.summary')
|
|
show_deleted: {
|
|
set(value) {
|
|
const postStream = this.get('model.postStream');
|
|
if (!postStream) { return; }
|
|
postStream.set('show_deleted', value);
|
|
return postStream.get('show_deleted') ? true : undefined;
|
|
},
|
|
get() {
|
|
return this.get('postStream.show_deleted') ? true : undefined;
|
|
}
|
|
},
|
|
|
|
@computed('model.postStream.summary')
|
|
filter: {
|
|
set(value) {
|
|
const postStream = this.get('model.postStream');
|
|
if (!postStream) { return; }
|
|
postStream.set('summary', value === "summary");
|
|
return postStream.get('summary') ? "summary" : undefined;
|
|
},
|
|
get() {
|
|
return this.get('postStream.summary') ? "summary" : undefined;
|
|
}
|
|
},
|
|
|
|
@computed('model.postStream.streamFilters.username_filters')
|
|
username_filters: {
|
|
set(value) {
|
|
const postStream = this.get('model.postStream');
|
|
if (!postStream) { return; }
|
|
postStream.set('streamFilters.username_filters', value);
|
|
return postStream.get('streamFilters.username_filters');
|
|
},
|
|
get() {
|
|
return this.get('postStream.streamFilters.username_filters');
|
|
}
|
|
},
|
|
|
|
_clearSelected: function() {
|
|
this.set('selectedPosts', []);
|
|
this.set('selectedReplies', []);
|
|
}.on('init'),
|
|
|
|
actions: {
|
|
deleteTopic() {
|
|
this.deleteTopic();
|
|
},
|
|
|
|
// Post related methods
|
|
replyToPost(post) {
|
|
const composerController = this.get('controllers.composer'),
|
|
quoteController = this.get('controllers.quote-button'),
|
|
quotedText = 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 {
|
|
|
|
const 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(post) {
|
|
const likeAction = post.get('likeAction');
|
|
if (likeAction && likeAction.get('canToggle')) {
|
|
likeAction.toggle(post);
|
|
}
|
|
},
|
|
|
|
recoverPost(post) {
|
|
// Recovering the first post recovers the topic instead
|
|
if (post.get('post_number') === 1) {
|
|
this.recoverTopic();
|
|
return;
|
|
}
|
|
post.recover();
|
|
},
|
|
|
|
deletePost(post) {
|
|
|
|
// Deleting the first post deletes the topic
|
|
if (post.get('post_number') === 1) {
|
|
this.deleteTopic();
|
|
return;
|
|
}
|
|
|
|
const 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() {
|
|
post.destroy(user);
|
|
}
|
|
},
|
|
{label: I18n.t("post.controls.delete_replies.yes_value"),
|
|
'class': 'btn-primary',
|
|
callback() {
|
|
Discourse.Post.deleteMany([post], [post]);
|
|
self.get('model.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).catch(function(error) {
|
|
popupAjaxError(error);
|
|
post.undoDeleteState();
|
|
});
|
|
}
|
|
},
|
|
|
|
editPost(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(post) {
|
|
if (!Discourse.User.current()) {
|
|
alert(I18n.t("bookmarks.not_bookmarked"));
|
|
return;
|
|
}
|
|
if (post) {
|
|
return post.toggleBookmark().catch(popupAjaxError);
|
|
} else {
|
|
return this.get("model").toggleBookmark();
|
|
}
|
|
},
|
|
|
|
jumpTop() {
|
|
this.get('controllers.topic-progress').send('jumpTop');
|
|
},
|
|
|
|
selectAll() {
|
|
const posts = this.get('model.postStream.posts'),
|
|
selectedPosts = this.get('selectedPosts');
|
|
if (posts) {
|
|
selectedPosts.addObjects(posts);
|
|
}
|
|
this.set('allPostsSelected', true);
|
|
},
|
|
|
|
deselectAll() {
|
|
this.get('selectedPosts').clear();
|
|
this.get('selectedReplies').clear();
|
|
this.set('allPostsSelected', false);
|
|
},
|
|
|
|
toggleParticipant(user) {
|
|
this.get('model.postStream').toggleParticipant(Em.get(user, 'username'));
|
|
},
|
|
|
|
editTopic() {
|
|
if (!this.get('model.details.can_edit')) return false;
|
|
|
|
this.set('editingTopic', true);
|
|
return false;
|
|
},
|
|
|
|
cancelEditingTopic() {
|
|
this.set('editingTopic', false);
|
|
this.rollbackBuffer();
|
|
},
|
|
|
|
toggleMultiSelect() {
|
|
this.toggleProperty('multiSelect');
|
|
},
|
|
|
|
finishedEditingTopic() {
|
|
if (!this.get('editingTopic')) { return; }
|
|
|
|
// save the modifications
|
|
const 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(popupAjaxError);
|
|
},
|
|
|
|
toggledSelectedPost(post) {
|
|
this.performTogglePost(post);
|
|
},
|
|
|
|
toggledSelectedPostReplies(post) {
|
|
const selectedReplies = this.get('selectedReplies');
|
|
if (this.performTogglePost(post)) {
|
|
selectedReplies.addObject(post);
|
|
} else {
|
|
selectedReplies.removeObject(post);
|
|
}
|
|
},
|
|
|
|
deleteSelected() {
|
|
const 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();
|
|
}
|
|
|
|
const selectedPosts = self.get('selectedPosts'),
|
|
selectedReplies = self.get('selectedReplies'),
|
|
postStream = self.get('model.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(post) {
|
|
post.expandHidden();
|
|
},
|
|
|
|
toggleVisibility() {
|
|
this.get('content').toggleStatus('visible');
|
|
},
|
|
|
|
toggleClosed() {
|
|
this.get('content').toggleStatus('closed');
|
|
},
|
|
|
|
recoverTopic() {
|
|
this.get('content').recover();
|
|
},
|
|
|
|
makeBanner() {
|
|
this.get('content').makeBanner();
|
|
},
|
|
|
|
removeBanner() {
|
|
this.get('content').removeBanner();
|
|
},
|
|
|
|
togglePinned() {
|
|
const value = this.get('model.pinned_at') ? false : true,
|
|
topic = this.get('content'),
|
|
until = this.get('model.pinnedInCategoryUntil');
|
|
|
|
// optimistic update
|
|
topic.setProperties({
|
|
pinned_at: value ? moment() : null,
|
|
pinned_globally: false,
|
|
pinned_until: value ? until : null
|
|
});
|
|
|
|
return topic.saveStatus("pinned", value, until);
|
|
},
|
|
|
|
pinGlobally() {
|
|
const topic = this.get('content'),
|
|
until = this.get('model.pinnedGloballyUntil');
|
|
|
|
// optimistic update
|
|
topic.setProperties({
|
|
pinned_at: moment(),
|
|
pinned_globally: true,
|
|
pinned_until: until
|
|
});
|
|
|
|
return topic.saveStatus("pinned_globally", true, until);
|
|
},
|
|
|
|
toggleArchived() {
|
|
this.get('content').toggleStatus('archived');
|
|
},
|
|
|
|
// Toggle the star on the topic
|
|
toggleStar() {
|
|
this.get('content').toggleStar();
|
|
},
|
|
|
|
clearPin() {
|
|
this.get('content').clearPin();
|
|
},
|
|
|
|
togglePinnedForUser() {
|
|
if (this.get('model.pinned_at')) {
|
|
if (this.get('pinned')) {
|
|
this.get('content').clearPin();
|
|
} else {
|
|
this.get('content').rePin();
|
|
}
|
|
}
|
|
},
|
|
|
|
replyAsNewTopic(post) {
|
|
const composerController = this.get('controllers.composer'),
|
|
quoteController = this.get('controllers.quote-button'),
|
|
quotedText = 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,
|
|
categoryId: this.get('category.id')
|
|
}).then(function() {
|
|
return Em.isEmpty(quotedText) ? Discourse.Post.loadQuote(post.get('id')) : quotedText;
|
|
}).then(function(q) {
|
|
const postUrl = "" + location.protocol + "//" + location.host + post.get('url'),
|
|
postLink = "[" + Handlebars.escapeExpression(self.get('model.title')) + "](" + postUrl + ")";
|
|
composerController.appendText(I18n.t("post.continue_discussion", { postLink: postLink }) + "\n\n" + q);
|
|
});
|
|
},
|
|
|
|
expandFirstPost(post) {
|
|
const 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() {
|
|
const self = this;
|
|
self.set('retrying', true);
|
|
this.get('model.postStream').refresh().then(function() {
|
|
self.set('retrying', false);
|
|
}, function() {
|
|
self.set('retrying', false);
|
|
});
|
|
},
|
|
|
|
toggleWiki(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(post) {
|
|
post.rebake();
|
|
},
|
|
|
|
unhidePost(post) {
|
|
post.unhide();
|
|
}
|
|
},
|
|
|
|
togglePinnedState() {
|
|
this.send('togglePinnedForUser');
|
|
},
|
|
|
|
showExpandButton: function() {
|
|
const post = this.get('post');
|
|
return post.get('post_number') === 1 && post.get('topic.expandable_first_post');
|
|
}.property(),
|
|
|
|
canMergeTopic: function() {
|
|
if (!this.get('model.details.can_move_posts')) return false;
|
|
return this.get('selectedPostsCount') > 0;
|
|
}.property('selectedPostsCount'),
|
|
|
|
canSplitTopic: function() {
|
|
if (!this.get('model.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') !== undefined;
|
|
}.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() {
|
|
const selectedPosts = this.get('selectedPosts');
|
|
|
|
if (this.get('allPostsSelected')) return true;
|
|
if (this.get('selectedPostsCount') === 0) return false;
|
|
|
|
let canDelete = true;
|
|
selectedPosts.forEach(function(p) {
|
|
if (!p.get('can_delete')) {
|
|
canDelete = false;
|
|
return false;
|
|
}
|
|
});
|
|
return canDelete;
|
|
}.property('selectedPostsCount'),
|
|
|
|
hasError: Ember.computed.or('model.notFoundHtml', 'model.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(post) {
|
|
this.get('selectedPosts').removeObject(post);
|
|
|
|
const selectedReplies = this.get('selectedReplies');
|
|
selectedReplies.removeObject(post);
|
|
|
|
const selectedReply = selectedReplies.findProperty('post_number', post.get('reply_to_post_number'));
|
|
if (selectedReply) { selectedReplies.removeObject(selectedReply); }
|
|
|
|
this.set('allPostsSelected', false);
|
|
},
|
|
|
|
postSelected(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('model.isPrivateMessage');
|
|
}.property('model.isPrivateMessage'),
|
|
|
|
loadingHTML: function() {
|
|
return spinnerHTML;
|
|
}.property(),
|
|
|
|
recoverTopic() {
|
|
this.get('content').recover();
|
|
},
|
|
|
|
deleteTopic() {
|
|
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('model.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('model.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() {
|
|
const 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() {
|
|
this.replyToPost();
|
|
},
|
|
|
|
performTogglePost(post) {
|
|
const 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
|
|
this.set('allPostsSelected', selectedPosts.length === this.get('model.posts_count'));
|
|
return true;
|
|
}
|
|
},
|
|
|
|
// If our current post is changed, notify the router
|
|
_currentPostChanged: function() {
|
|
const currentPost = this.get('model.currentPost');
|
|
if (currentPost) {
|
|
this.send('postChangedRoute', currentPost);
|
|
}
|
|
}.observes('model.currentPost'),
|
|
|
|
readPosts(topicId, postNumbers) {
|
|
const postStream = this.get('model.postStream');
|
|
|
|
if (postStream.get('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);
|
|
}
|
|
});
|
|
|
|
const max = _.max(postNumbers);
|
|
if(max > this.get('model.last_read_post_number')){
|
|
this.set('model.sast_read_post_number', max);
|
|
}
|
|
}
|
|
},
|
|
|
|
// Called the the topmost visible post on the page changes.
|
|
topVisibleChanged(post) {
|
|
if (!post) { return; }
|
|
|
|
const postStream = this.get('model.postStream'),
|
|
firstLoadedPost = postStream.get('firstLoadedPost');
|
|
|
|
this.set('model.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.
|
|
const $body = $('body');
|
|
let $elem = $('#post-cloak-' + post.get('post_number'));
|
|
const 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
|
|
const 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(post) {
|
|
if (!post) { return; }
|
|
|
|
const postStream = this.get('model.postStream'),
|
|
lastLoadedPost = postStream.get('lastLoadedPost');
|
|
|
|
this.set('controllers.topic-progress.progressPosition', postStream.progressIndexOfPost(post));
|
|
|
|
if (lastLoadedPost && lastLoadedPost === post) {
|
|
postStream.appendMore();
|
|
}
|
|
},
|
|
|
|
_showFooter: function() {
|
|
const showFooter = this.get("model.postStream.loaded") && this.get("model.postStream.loadedAllPosts");
|
|
this.set("controllers.application.showFooter", showFooter);
|
|
}.observes("model.postStream.{loaded,loadedAllPosts}")
|
|
|
|
});
|