FEATURE: Added 'select +below' and 'select +all replies' options to selecting posts
This commit is contained in:
parent
b15059418b
commit
1b4483c942
|
@ -1,55 +1,54 @@
|
||||||
import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import DiscourseURL from 'discourse/lib/url';
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
|
export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
|
topicController: Ember.inject.controller("topic"),
|
||||||
|
|
||||||
// Modal related to changing the ownership of posts
|
|
||||||
export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, {
|
|
||||||
topicController: Ember.inject.controller('topic'),
|
|
||||||
selectedPosts: Em.computed.alias('topicController.selectedPosts'),
|
|
||||||
saving: false,
|
saving: false,
|
||||||
new_user: null,
|
new_user: null,
|
||||||
|
|
||||||
buttonDisabled: function() {
|
selectedPostsCount: Ember.computed.alias("topicController.selectedPostsCount"),
|
||||||
if (this.get('saving')) return true;
|
selectedPostsUsername: Ember.computed.alias("topicController.selectedPostsUsername"),
|
||||||
return Ember.isEmpty(this.get('new_user'));
|
|
||||||
}.property('saving', 'new_user'),
|
|
||||||
|
|
||||||
buttonTitle: function() {
|
@computed("saving", "new_user")
|
||||||
if (this.get('saving')) return I18n.t('saving');
|
buttonDisabled(saving, newUser) {
|
||||||
return I18n.t('topic.change_owner.action');
|
return saving || Ember.isEmpty(newUser);
|
||||||
}.property('saving'),
|
},
|
||||||
|
|
||||||
onShow: function() {
|
@computed("saving")
|
||||||
|
buttonTitle(saving) {
|
||||||
|
return saving ? I18n.t("saving") : I18n.t("topic.change_owner.action");
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow() {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
saving: false,
|
saving: false,
|
||||||
new_user: ''
|
new_user: ""
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
changeOwnershipOfPosts: function() {
|
changeOwnershipOfPosts() {
|
||||||
this.set('saving', true);
|
this.set("saving", true);
|
||||||
|
|
||||||
var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }),
|
const options = {
|
||||||
self = this,
|
post_ids: this.get("topicController.selectedPostIds"),
|
||||||
saveOpts = {
|
username: this.get("new_user"),
|
||||||
post_ids: postIds,
|
};
|
||||||
username: this.get('new_user')
|
|
||||||
};
|
|
||||||
|
|
||||||
Discourse.Topic.changeOwners(this.get('topicController.model.id'), saveOpts).then(function() {
|
Discourse.Topic.changeOwners(this.get("topicController.model.id"), options).then(() => {
|
||||||
// success
|
this.send("closeModal");
|
||||||
self.send('closeModal');
|
this.get("topicController").send("deselectAll");
|
||||||
self.get('topicController').send('deselectAll');
|
if (this.get("topicController.multiSelect")) {
|
||||||
if (self.get('topicController.multiSelect')) {
|
this.get("topicController").send("toggleMultiSelect");
|
||||||
self.get('topicController').send('toggleMultiSelect');
|
|
||||||
}
|
}
|
||||||
Em.run.next(() => { DiscourseURL.routeTo(self.get("topicController.model.url")); });
|
Ember.run.next(() => DiscourseURL.routeTo(this.get("topicController.model.url")));
|
||||||
}, function() {
|
}, () => {
|
||||||
// failure
|
this.flash(I18n.t("topic.change_owner.error"), "alert-error");
|
||||||
self.flash(I18n.t('topic.change_owner.error'), 'alert-error');
|
this.set("saving", false);
|
||||||
self.set('saving', false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,64 +1,53 @@
|
||||||
import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
import { movePosts, mergeTopic } from "discourse/models/topic";
|
||||||
import { movePosts, mergeTopic } from 'discourse/models/topic';
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import DiscourseURL from 'discourse/lib/url';
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
// Modal related to merging of topics
|
export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, {
|
topicController: Ember.inject.controller("topic"),
|
||||||
topicController: Ember.inject.controller('topic'),
|
|
||||||
|
|
||||||
saving: false,
|
saving: false,
|
||||||
selectedTopicId: null,
|
selectedTopicId: null,
|
||||||
|
|
||||||
selectedPosts: Em.computed.alias('topicController.selectedPosts'),
|
selectedPostsCount: Ember.computed.alias("topicController.selectedPostsCount"),
|
||||||
selectedReplies: Em.computed.alias('topicController.selectedReplies'),
|
|
||||||
allPostsSelected: Em.computed.alias('topicController.allPostsSelected'),
|
|
||||||
|
|
||||||
buttonDisabled: function() {
|
@computed("saving", "selectedTopicId")
|
||||||
if (this.get('saving')) return true;
|
buttonDisabled(saving, selectedTopicId) {
|
||||||
return Ember.isEmpty(this.get('selectedTopicId'));
|
return saving || Ember.isEmpty(selectedTopicId);
|
||||||
}.property('selectedTopicId', 'saving'),
|
},
|
||||||
|
|
||||||
buttonTitle: function() {
|
@computed("saving")
|
||||||
if (this.get('saving')) return I18n.t('saving');
|
buttonTitle(saving) {
|
||||||
return I18n.t('topic.merge_topic.title');
|
return saving ? I18n.t("saving") : I18n.t("topic.merge_topic.title");
|
||||||
}.property('saving'),
|
},
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
this.set('modal.modalClass', 'split-modal');
|
this.set("modal.modalClass", "split-modal");
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
movePostsToExistingTopic() {
|
movePostsToExistingTopic() {
|
||||||
const topicId = this.get('model.id');
|
const topicId = this.get("model.id");
|
||||||
|
|
||||||
this.set('saving', true);
|
this.set("saving", true);
|
||||||
|
|
||||||
let promise = null;
|
let promise = this.get("topicController.selectedAllPosts") ?
|
||||||
if (this.get('allPostsSelected')) {
|
mergeTopic(topicId, this.get("selectedTopicId")) :
|
||||||
promise = mergeTopic(topicId, this.get('selectedTopicId'));
|
movePosts(topicId, {
|
||||||
} else {
|
destination_topic_id: this.get("selectedTopicId"),
|
||||||
const postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); });
|
post_ids: this.get("topicController.selectedPostIds")
|
||||||
const replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); });
|
|
||||||
|
|
||||||
promise = movePosts(topicId, {
|
|
||||||
destination_topic_id: this.get('selectedTopicId'),
|
|
||||||
post_ids: postIds,
|
|
||||||
reply_post_ids: replyPostIds
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const self = this;
|
promise.then(result => {
|
||||||
promise.then(function(result) {
|
this.send("closeModal");
|
||||||
// Posts moved
|
this.get("topicController").send("toggleMultiSelect");
|
||||||
self.send('closeModal');
|
Ember.run.next(() => DiscourseURL.routeTo(result.url));
|
||||||
self.get('topicController').send('toggleMultiSelect');
|
}).catch(() => {
|
||||||
Em.run.next(function() { DiscourseURL.routeTo(result.url); });
|
this.flash(I18n.t("topic.merge_topic.error"));
|
||||||
}).catch(function() {
|
}).finally(() => {
|
||||||
self.flash(I18n.t('topic.merge_topic.error'));
|
this.set("saving", false);
|
||||||
}).finally(function() {
|
|
||||||
self.set('saving', false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +1,58 @@
|
||||||
import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
import { extractError } from "discourse/lib/ajax-error";
|
||||||
import { extractError } from 'discourse/lib/ajax-error';
|
import { movePosts } from "discourse/models/topic";
|
||||||
import { movePosts } from 'discourse/models/topic';
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import DiscourseURL from 'discourse/lib/url';
|
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
// Modal related to auto closing of topics
|
export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, {
|
|
||||||
topicName: null,
|
topicName: null,
|
||||||
saving: false,
|
saving: false,
|
||||||
categoryId: null,
|
categoryId: null,
|
||||||
|
|
||||||
topicController: Ember.inject.controller('topic'),
|
topicController: Ember.inject.controller("topic"),
|
||||||
selectedPosts: Em.computed.alias('topicController.selectedPosts'),
|
selectedPostsCount: Ember.computed.alias("topicController.selectedPostsCount"),
|
||||||
selectedReplies: Em.computed.alias('topicController.selectedReplies'),
|
|
||||||
allPostsSelected: Em.computed.alias('topicController.allPostsSelected'),
|
|
||||||
|
|
||||||
buttonDisabled: function() {
|
@computed("saving", "topicName")
|
||||||
if (this.get('saving')) return true;
|
buttonDisabled(saving, topicName) {
|
||||||
return Ember.isEmpty(this.get('topicName'));
|
return saving || Ember.isEmpty(topicName);
|
||||||
}.property('saving', 'topicName'),
|
},
|
||||||
|
|
||||||
buttonTitle: function() {
|
@computed("saving")
|
||||||
if (this.get('saving')) return I18n.t('saving');
|
buttonTitle(saving) {
|
||||||
return I18n.t('topic.split_topic.action');
|
return saving ? I18n.t("saving") : I18n.t("topic.split_topic.action");
|
||||||
}.property('saving'),
|
},
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
'modal.modalClass': 'split-modal',
|
"modal.modalClass": "split-modal",
|
||||||
saving: false,
|
saving: false,
|
||||||
categoryId: null,
|
categoryId: null,
|
||||||
topicName: ''
|
topicName: ""
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
movePostsToNewTopic() {
|
movePostsToNewTopic() {
|
||||||
this.set('saving', true);
|
this.set("saving", true);
|
||||||
|
|
||||||
const postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }),
|
const options = {
|
||||||
replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); }),
|
title: this.get("topicName"),
|
||||||
self = this,
|
post_ids: this.get("topicController.selectedPostIds"),
|
||||||
categoryId = this.get('categoryId'),
|
category_id: this.get("categoryId")
|
||||||
saveOpts = {
|
};
|
||||||
title: this.get('topicName'),
|
|
||||||
post_ids: postIds,
|
|
||||||
reply_post_ids: replyPostIds
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!Ember.isNone(categoryId)) { saveOpts.category_id = categoryId; }
|
movePosts(this.get("model.id"), options).then(result => {
|
||||||
|
this.send("closeModal");
|
||||||
movePosts(this.get('model.id'), saveOpts).then(function(result) {
|
this.get("topicController").send("toggleMultiSelect");
|
||||||
// Posts moved
|
Ember.run.next(() => DiscourseURL.routeTo(result.url));
|
||||||
self.send('closeModal');
|
}).catch(xhr => {
|
||||||
self.get('topicController').send('toggleMultiSelect');
|
this.flash(extractError(xhr, I18n.t("topic.split_topic.error")));
|
||||||
Ember.run.next(function() { DiscourseURL.routeTo(result.url); });
|
}).finally(() => {
|
||||||
}).catch(function(xhr) {
|
this.set("saving", false);
|
||||||
self.flash(extractError(xhr, I18n.t('topic.split_topic.error')));
|
|
||||||
}).finally(function() {
|
|
||||||
self.set('saving', false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,27 +1,25 @@
|
||||||
import BufferedContent from 'discourse/mixins/buffered-content';
|
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 { popupAjaxError } from 'discourse/lib/ajax-error';
|
|
||||||
import computed from 'ember-addons/ember-computed-decorators';
|
|
||||||
import Composer from 'discourse/models/composer';
|
import Composer from 'discourse/models/composer';
|
||||||
import DiscourseURL from 'discourse/lib/url';
|
import DiscourseURL from 'discourse/lib/url';
|
||||||
import Post from 'discourse/models/post';
|
import Post from 'discourse/models/post';
|
||||||
|
import Quote from 'discourse/lib/quote';
|
||||||
|
import QuoteState from 'discourse/lib/quote-state';
|
||||||
|
import Topic from 'discourse/models/topic';
|
||||||
import debounce from 'discourse/lib/debounce';
|
import debounce from 'discourse/lib/debounce';
|
||||||
import isElementInViewport from "discourse/lib/is-element-in-viewport";
|
import isElementInViewport from "discourse/lib/is-element-in-viewport";
|
||||||
import QuoteState from 'discourse/lib/quote-state';
|
import { ajax } from 'discourse/lib/ajax';
|
||||||
import { userPath } from 'discourse/lib/url';
|
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
|
||||||
import { extractLinkMeta } from 'discourse/lib/render-topic-featured-link';
|
import { extractLinkMeta } from 'discourse/lib/render-topic-featured-link';
|
||||||
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
import { spinnerHTML } from 'discourse/helpers/loading-spinner';
|
||||||
|
import { userPath } from 'discourse/lib/url';
|
||||||
|
|
||||||
export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
export default Ember.Controller.extend(BufferedContent, {
|
||||||
composer: Ember.inject.controller(),
|
composer: Ember.inject.controller(),
|
||||||
application: Ember.inject.controller(),
|
application: Ember.inject.controller(),
|
||||||
multiSelect: false,
|
multiSelect: false,
|
||||||
allPostsSelected: false,
|
selectedPostIds: null,
|
||||||
editingTopic: false,
|
editingTopic: false,
|
||||||
selectedPosts: null,
|
|
||||||
selectedReplies: null,
|
|
||||||
queryParams: ['filter', 'username_filters'],
|
queryParams: ['filter', 'username_filters'],
|
||||||
loadedAllPosts: Ember.computed.or('model.postStream.loadedAllPosts', 'model.postStream.loadingLastPost'),
|
loadedAllPosts: Ember.computed.or('model.postStream.loadedAllPosts', 'model.postStream.loadingLastPost'),
|
||||||
enteredAt: null,
|
enteredAt: null,
|
||||||
|
@ -36,29 +34,26 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
canRemoveTopicFeaturedLink: Ember.computed.and('canEditTopicFeaturedLink', 'buffered.featured_link'),
|
canRemoveTopicFeaturedLink: Ember.computed.and('canEditTopicFeaturedLink', 'buffered.featured_link'),
|
||||||
|
|
||||||
updateQueryParams() {
|
updateQueryParams() {
|
||||||
const postStream = this.get('model.postStream');
|
this.setProperties(this.get('model.postStream.streamFilters'));
|
||||||
this.setProperties(postStream.get('streamFilters'));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_titleChanged: function() {
|
@observes('model.title', 'category')
|
||||||
|
_titleChanged() {
|
||||||
const title = this.get('model.title');
|
const title = this.get('model.title');
|
||||||
if (!Ember.isEmpty(title)) {
|
if (!Ember.isEmpty(title)) {
|
||||||
|
// force update lazily loaded titles
|
||||||
// Note normally you don't have to trigger this, but topic titles can be updated
|
|
||||||
// and are sometimes lazily loaded.
|
|
||||||
this.send('refreshTitle');
|
this.send('refreshTitle');
|
||||||
}
|
}
|
||||||
}.observes('model.title', 'category'),
|
},
|
||||||
|
|
||||||
@computed('site.mobileView', 'model.posts_count')
|
@computed('site.mobileView', 'model.posts_count')
|
||||||
showSelectedPostsAtBottom(mobileView, postsCount) {
|
showSelectedPostsAtBottom(mobileView, postsCount) {
|
||||||
return mobileView && (postsCount > 3);
|
return mobileView && postsCount > 3;
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed('model.postStream.posts')
|
@computed('model.postStream.posts', 'model.postStream.postsWithPlaceholders')
|
||||||
postsToRender() {
|
postsToRender(posts, postsWithPlaceholders) {
|
||||||
return this.capabilities.isAndroid ? this.get('model.postStream.posts')
|
return this.capabilities.isAndroid ? posts : postsWithPlaceholders;
|
||||||
: this.get('model.postStream.postsWithPlaceholders');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed('model.postStream.loadingFilter')
|
@computed('model.postStream.loadingFilter')
|
||||||
|
@ -66,17 +61,17 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
return this.capabilities.isAndroid && loading;
|
return this.capabilities.isAndroid && loading;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@computed('model')
|
@computed('model')
|
||||||
pmPath(model) {
|
pmPath(topic) {
|
||||||
return this.currentUser && this.currentUser.pmPath(model);
|
return this.currentUser && this.currentUser.pmPath(topic);
|
||||||
},
|
},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super();
|
this._super();
|
||||||
this.set('selectedPosts', []);
|
this.setProperties({
|
||||||
this.set('selectedReplies', []);
|
selectedPostIds: [],
|
||||||
this.set('quoteState', new QuoteState());
|
quoteState: new QuoteState(),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
showCategoryChooser: Ember.computed.not("model.isPrivateMessage"),
|
showCategoryChooser: Ember.computed.not("model.isPrivateMessage"),
|
||||||
|
@ -89,22 +84,22 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
DiscourseURL.routeTo(url);
|
DiscourseURL.routeTo(url);
|
||||||
},
|
},
|
||||||
|
|
||||||
selectedQuery: function() {
|
@computed
|
||||||
|
selectedQuery() {
|
||||||
return post => this.postSelected(post);
|
return post => this.postSelected(post);
|
||||||
}.property(),
|
},
|
||||||
|
|
||||||
@computed('model.isPrivateMessage', 'model.category.id')
|
@computed('model.isPrivateMessage', 'model.category.id')
|
||||||
canEditTopicFeaturedLink(isPrivateMessage, categoryId) {
|
canEditTopicFeaturedLink(isPrivateMessage, categoryId) {
|
||||||
if (!this.siteSettings.topic_featured_link_enabled || isPrivateMessage) { return false; }
|
if (!this.siteSettings.topic_featured_link_enabled || isPrivateMessage) { return false; }
|
||||||
|
|
||||||
const categoryIds = this.site.get('topic_featured_link_allowed_category_ids');
|
const categoryIds = this.site.get('topic_featured_link_allowed_category_ids');
|
||||||
return categoryIds === undefined || !categoryIds.length || categoryIds.indexOf(categoryId) !== -1;
|
return categoryIds === undefined || !categoryIds.length || categoryIds.includes(categoryId);
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed('model')
|
@computed('model')
|
||||||
featuredLinkDomain(topic) {
|
featuredLinkDomain(topic) {
|
||||||
const meta = extractLinkMeta(topic);
|
return extractLinkMeta(topic).domain;
|
||||||
return meta.domain;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed('model.isPrivateMessage')
|
@computed('model.isPrivateMessage')
|
||||||
|
@ -258,16 +253,15 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
// Archive a PM (as opposed to archiving a topic)
|
// Archive a PM (as opposed to archiving a topic)
|
||||||
toggleArchiveMessage() {
|
toggleArchiveMessage() {
|
||||||
const topic = this.get('model');
|
const topic = this.get('model');
|
||||||
|
|
||||||
if (topic.get('archiving')) { return; }
|
if (topic.get('archiving')) { return; }
|
||||||
|
|
||||||
|
const backToInbox = () => this.goToInbox(topic.get("inboxGroupName"));
|
||||||
|
|
||||||
if (topic.get('message_archived')) {
|
if (topic.get('message_archived')) {
|
||||||
topic.moveToInbox().then(()=>{
|
topic.moveToInbox().then(backToInbox);
|
||||||
this.gotoInbox(topic.get("inboxGroupName"));
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
topic.archiveMessage().then(()=>{
|
topic.archiveMessage().then(backToInbox);
|
||||||
this.gotoInbox(topic.get("inboxGroupName"));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -275,10 +269,11 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
replyToPost(post) {
|
replyToPost(post) {
|
||||||
const composerController = this.get('composer');
|
const composerController = this.get('composer');
|
||||||
const topic = post ? post.get('topic') : this.get('model');
|
const topic = post ? post.get('topic') : this.get('model');
|
||||||
|
|
||||||
const quoteState = this.get('quoteState');
|
const quoteState = this.get('quoteState');
|
||||||
const postStream = this.get('model.postStream');
|
const postStream = this.get('model.postStream');
|
||||||
|
|
||||||
if (!postStream) return;
|
if (!postStream) return;
|
||||||
|
|
||||||
const quotedPost = postStream.findLoadedPost(quoteState.postId);
|
const quotedPost = postStream.findLoadedPost(quoteState.postId);
|
||||||
const quotedText = Quote.build(quotedPost, quoteState.buffer);
|
const quotedText = Quote.build(quotedPost, quoteState.buffer);
|
||||||
|
|
||||||
|
@ -290,7 +285,6 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
composerController.set('content.composeState', Composer.OPEN);
|
composerController.set('content.composeState', Composer.OPEN);
|
||||||
this.appEvents.trigger('composer:insert-block', quotedText.trim());
|
this.appEvents.trigger('composer:insert-block', quotedText.trim());
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
action: Composer.REPLY,
|
action: Composer.REPLY,
|
||||||
draftKey: topic.get('draft_key'),
|
draftKey: topic.get('draft_key'),
|
||||||
|
@ -311,78 +305,94 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
},
|
},
|
||||||
|
|
||||||
recoverPost(post) {
|
recoverPost(post) {
|
||||||
// Recovering the first post recovers the topic instead
|
post.get("post_number") === 1 ? this.recoverTopic() : this.recover();
|
||||||
if (post.get('post_number') === 1) {
|
|
||||||
this.recoverTopic();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
post.recover();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deletePost(post) {
|
deletePost(post) {
|
||||||
|
|
||||||
// Deleting the first post deletes the topic
|
|
||||||
if (post.get('post_number') === 1) {
|
if (post.get('post_number') === 1) {
|
||||||
return this.deleteTopic();
|
return this.deleteTopic();
|
||||||
} else if (!post.can_delete) {
|
} else if (!post.can_delete) {
|
||||||
// check if current user can delete post
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = Discourse.User.current(),
|
const user = this.currentUser;
|
||||||
replyCount = post.get('reply_count'),
|
const refresh = () => this.appEvents.trigger('post-stream:refresh');
|
||||||
self = this;
|
const hasReplies = post.get('reply_count') > 0;
|
||||||
|
const loadedPosts = this.get('model.postStream.posts');
|
||||||
|
|
||||||
// If the user is staff and the post has replies, ask if they want to delete replies too.
|
if (user.get('staff') && hasReplies) {
|
||||||
if (user.get('staff') && replyCount > 0) {
|
ajax(`/posts/${post.id}/reply-ids.json`).then(replies => {
|
||||||
bootbox.dialog(I18n.t("post.controls.delete_replies.confirm", {count: replyCount}), [
|
const buttons = [];
|
||||||
{label: I18n.t("cancel"),
|
|
||||||
'class': 'btn-danger right'},
|
buttons.push({
|
||||||
{label: I18n.t("post.controls.delete_replies.no_value"),
|
label: I18n.t('cancel'),
|
||||||
|
'class': 'btn-danger right'
|
||||||
|
});
|
||||||
|
|
||||||
|
buttons.push({
|
||||||
|
label: I18n.t('post.controls.delete_replies.just_the_post'),
|
||||||
callback() {
|
callback() {
|
||||||
post.destroy(user);
|
post.destroy(user)
|
||||||
}
|
.then(refresh)
|
||||||
},
|
.catch(error => {
|
||||||
{label: I18n.t("post.controls.delete_replies.yes_value"),
|
popupAjaxError(error);
|
||||||
'class': 'btn-primary',
|
post.undoDeleteState();
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (replies.some(r => r.level > 1)) {
|
||||||
|
buttons.push({
|
||||||
|
label: I18n.t('post.controls.delete_replies.all_replies', { count: replies.length }),
|
||||||
|
callback() {
|
||||||
|
loadedPosts.forEach(p => (p === post || replies.some(r => r.id === p.id)) && p.setDeletedState(user));
|
||||||
|
Post.deleteMany([post.id, ...replies.map(r => r.id)])
|
||||||
|
.then(refresh)
|
||||||
|
.catch(popupAjaxError);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
]);
|
|
||||||
} else {
|
const directReplyIds = replies.filter(r => r.level === 1).map(r => r.id);
|
||||||
return post.destroy(user).then(() => {
|
|
||||||
this.appEvents.trigger('post-stream:refresh');
|
buttons.push({
|
||||||
}).catch(error => {
|
label: I18n.t('post.controls.delete_replies.direct_replies', { count: directReplyIds.length }),
|
||||||
popupAjaxError(error);
|
'class': 'btn-primary',
|
||||||
post.undoDeleteState();
|
callback() {
|
||||||
|
loadedPosts.forEach(p => (p === post || directReplyIds.includes(p.id)) && p.setDeletedState(user));
|
||||||
|
Post.deleteMany([post.id, ...directReplyIds])
|
||||||
|
.then(refresh)
|
||||||
|
.catch(popupAjaxError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bootbox.dialog(I18n.t("post.controls.delete_replies.confirm"), buttons);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
return post.destroy(user)
|
||||||
|
.then(refresh)
|
||||||
|
.catch(error => {
|
||||||
|
popupAjaxError(error);
|
||||||
|
post.undoDeleteState();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
editPost(post) {
|
editPost(post) {
|
||||||
if (!Discourse.User.current()) {
|
if (!this.currentUser) {
|
||||||
return bootbox.alert(I18n.t('post.controls.edit_anonymous'));
|
return bootbox.alert(I18n.t('post.controls.edit_anonymous'));
|
||||||
}
|
} else if (!post.can_edit) {
|
||||||
|
|
||||||
// check if current user can edit post
|
|
||||||
if (!post.can_edit) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const composer = this.get('composer'),
|
const composer = this.get("composer");
|
||||||
composerModel = composer.get('model'),
|
const composerModel = composer.get("model");
|
||||||
opts = {
|
const opts = {
|
||||||
post: post,
|
post,
|
||||||
action: Composer.EDIT,
|
action: Composer.EDIT,
|
||||||
draftKey: post.get('topic.draft_key'),
|
draftKey: post.get("topic.draft_key"),
|
||||||
draftSequence: post.get('topic.draft_sequence')
|
draftSequence: post.get("topic.draft_sequence")
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cancel and reopen the composer for the first post
|
// Cancel and reopen the composer for the first post
|
||||||
if (composerModel && (post.get('firstPost') || composerModel.get('editingFirstPost'))) {
|
if (composerModel && (post.get('firstPost') || composerModel.get('editingFirstPost'))) {
|
||||||
|
@ -394,10 +404,8 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
|
|
||||||
toggleBookmark(post) {
|
toggleBookmark(post) {
|
||||||
if (!this.currentUser) {
|
if (!this.currentUser) {
|
||||||
alert(I18n.t("bookmarks.not_bookmarked"));
|
return bootbox.alert(I18n.t("bookmarks.not_bookmarked"));
|
||||||
return;
|
} else if (post) {
|
||||||
}
|
|
||||||
if (post) {
|
|
||||||
return post.toggleBookmark().catch(popupAjaxError);
|
return post.toggleBookmark().catch(popupAjaxError);
|
||||||
} else {
|
} else {
|
||||||
return this.get("model").toggleBookmark().then(changedIds => {
|
return this.get("model").toggleBookmark().then(changedIds => {
|
||||||
|
@ -408,14 +416,16 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
},
|
},
|
||||||
|
|
||||||
jumpToIndex(index) {
|
jumpToIndex(index) {
|
||||||
this._jumpToPostId(this.get('model.postStream.stream')[index-1]);
|
this._jumpToPostId(this.get('model.postStream.stream')[index - 1]);
|
||||||
},
|
},
|
||||||
|
|
||||||
jumpToPostPrompt() {
|
jumpToPostPrompt() {
|
||||||
const postText = prompt(I18n.t('topic.progress.jump_prompt_long'));
|
const postText = prompt(I18n.t('topic.progress.jump_prompt_long'));
|
||||||
if (postText === null) { return; }
|
if (postText === null) { return; }
|
||||||
|
|
||||||
const postNumber = parseInt(postText, 10);
|
const postNumber = parseInt(postText, 10);
|
||||||
if (postNumber === 0) { return; }
|
if (postNumber === 0) { return; }
|
||||||
|
|
||||||
this._jumpToPostId(this.get('model.postStream').findPostIdForPostNumber(postNumber));
|
this._jumpToPostId(this.get('model.postStream').findPostIdForPostNumber(postNumber));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -428,6 +438,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
const closest = postStream.closestPostNumberFor(postNumber);
|
const closest = postStream.closestPostNumberFor(postNumber);
|
||||||
postId = postStream.findPostIdForPostNumber(closest);
|
postId = postStream.findPostIdForPostNumber(closest);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._jumpToPostId(postId);
|
this._jumpToPostId(postId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -443,96 +454,51 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
this._jumpToPostId(this.get('model.last_read_post_id'));
|
this._jumpToPostId(this.get('model.last_read_post_id'));
|
||||||
},
|
},
|
||||||
|
|
||||||
selectAll() {
|
|
||||||
const posts = this.get('model.postStream.posts');
|
|
||||||
const selectedPosts = this.get('selectedPosts');
|
|
||||||
if (posts) {
|
|
||||||
selectedPosts.addObjects(posts);
|
|
||||||
}
|
|
||||||
this.set('allPostsSelected', true);
|
|
||||||
this.appEvents.trigger('post-stream:refresh', { force: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
deselectAll() {
|
|
||||||
this.get('selectedPosts').clear();
|
|
||||||
this.get('selectedReplies').clear();
|
|
||||||
this.set('allPostsSelected', false);
|
|
||||||
this.appEvents.trigger('post-stream:refresh', { force: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleParticipant(user) {
|
|
||||||
const postStream = this.get('model.postStream');
|
|
||||||
postStream.toggleParticipant(Ember.get(user, 'username')).then(() => {
|
|
||||||
this.updateQueryParams();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
editTopic() {
|
|
||||||
if (!this.get('model.details.can_edit')) return false;
|
|
||||||
|
|
||||||
this.set('editingTopic', true);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelEditingTopic() {
|
|
||||||
this.set('editingTopic', false);
|
|
||||||
this.rollbackBuffer();
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleMultiSelect() {
|
toggleMultiSelect() {
|
||||||
this.toggleProperty('multiSelect');
|
this.toggleProperty('multiSelect');
|
||||||
this.appEvents.trigger('post-stream:refresh', { force: true });
|
this.appEvents.trigger('post-stream:refresh', { force: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
finishedEditingTopic() {
|
selectAll() {
|
||||||
if (!this.get('editingTopic')) { return; }
|
this.set('selectedPostIds', [...this.get('model.postStream.stream')]);
|
||||||
|
this.appEvents.trigger('post-stream:refresh', { force: true });
|
||||||
// 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) {
|
deselectAll() {
|
||||||
this.performTogglePost(post);
|
this.set('selectedPostIds', []);
|
||||||
|
this.appEvents.trigger('post-stream:refresh', { force: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
toggledSelectedPostReplies(post) {
|
togglePostSelection(post) {
|
||||||
const selectedReplies = this.get('selectedReplies');
|
const selected = this.get('selectedPostIds');
|
||||||
if (this.performTogglePost(post)) {
|
selected.includes(post.id) ? selected.removeObject(post.id) : selected.addObject(post.id);
|
||||||
selectedReplies.addObject(post);
|
},
|
||||||
} else {
|
|
||||||
selectedReplies.removeObject(post);
|
selectReplies(post) {
|
||||||
}
|
ajax(`/posts/${post.id}/reply-ids.json`).then(replies => {
|
||||||
|
const replyIds = replies.map(r => r.id);
|
||||||
|
this.get('selectedPostIds').pushObjects([post.id, ...replyIds]);
|
||||||
|
this.appEvents.trigger('post-stream:refresh', { force: true });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
selectBelow(post) {
|
||||||
|
const stream = [...this.get('model.postStream.stream')];
|
||||||
|
const below = stream.slice(stream.indexOf(post.id));
|
||||||
|
this.get('selectedPostIds').pushObjects(below);
|
||||||
this.appEvents.trigger('post-stream:refresh', { force: true });
|
this.appEvents.trigger('post-stream:refresh', { force: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteSelected() {
|
deleteSelected() {
|
||||||
|
const user = this.currentUser;
|
||||||
|
|
||||||
bootbox.confirm(I18n.t("post.delete.confirm", { count: this.get('selectedPostsCount')}), result => {
|
bootbox.confirm(I18n.t("post.delete.confirm", { count: this.get('selectedPostsCount')}), result => {
|
||||||
if (result) {
|
if (result) {
|
||||||
|
|
||||||
// If all posts are selected, it's the same thing as deleting the topic
|
// If all posts are selected, it's the same thing as deleting the topic
|
||||||
if (this.get('allPostsSelected')) {
|
if (this.get('selectedAllPosts')) return this.deleteTopic();
|
||||||
return this.deleteTopic();
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedPosts = this.get('selectedPosts');
|
|
||||||
const selectedReplies = this.get('selectedReplies');
|
|
||||||
const postStream = this.get('model.postStream');
|
|
||||||
|
|
||||||
Discourse.Post.deleteMany(selectedPosts, selectedReplies);
|
|
||||||
postStream.get('posts').forEach(p => {
|
|
||||||
if (this.postSelected(p)) {
|
|
||||||
p.set('deleted_at', new Date());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
Post.deleteMany(this.get('selectedPostIds'));
|
||||||
|
this.get('model.postStream.posts').forEach(p => this.postSelected(p) && p.setDeletedState(user));
|
||||||
this.send('toggleMultiSelect');
|
this.send('toggleMultiSelect');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -541,13 +507,48 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
mergePosts() {
|
mergePosts() {
|
||||||
bootbox.confirm(I18n.t("post.merge.confirm", { count: this.get('selectedPostsCount') }), result => {
|
bootbox.confirm(I18n.t("post.merge.confirm", { count: this.get('selectedPostsCount') }), result => {
|
||||||
if (result) {
|
if (result) {
|
||||||
const selectedPosts = this.get('selectedPosts');
|
Post.mergePosts(this.get("selectedPostIds"));
|
||||||
Post.mergePosts(selectedPosts);
|
|
||||||
this.send('toggleMultiSelect');
|
this.send('toggleMultiSelect');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
changePostOwner(post) {
|
||||||
|
this.get("selectedPostIds").addObject(post.id);
|
||||||
|
this.send('changeOwner');
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleParticipant(user) {
|
||||||
|
this.get("model.postStream")
|
||||||
|
.toggleParticipant(user.get("username"))
|
||||||
|
.then(() => this.updateQueryParams);
|
||||||
|
},
|
||||||
|
|
||||||
|
editTopic() {
|
||||||
|
if (this.get('model.details.can_edit')) {
|
||||||
|
this.set('editingTopic', true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelEditingTopic() {
|
||||||
|
this.set('editingTopic', false);
|
||||||
|
this.rollbackBuffer();
|
||||||
|
},
|
||||||
|
|
||||||
|
finishedEditingTopic() {
|
||||||
|
if (!this.get('editingTopic')) { return; }
|
||||||
|
|
||||||
|
// save the modifications
|
||||||
|
const props = this.get('buffered.buffer');
|
||||||
|
|
||||||
|
Topic.update(this.get('model'), props).then(() => {
|
||||||
|
// We roll back on success here because `update` saves the properties to the topic
|
||||||
|
this.rollbackBuffer();
|
||||||
|
this.set('editingTopic', false);
|
||||||
|
}).catch(popupAjaxError);
|
||||||
|
},
|
||||||
|
|
||||||
expandHidden(post) {
|
expandHidden(post) {
|
||||||
return post.expandHidden();
|
return post.expandHidden();
|
||||||
},
|
},
|
||||||
|
@ -665,13 +666,9 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
},
|
},
|
||||||
|
|
||||||
retryLoading() {
|
retryLoading() {
|
||||||
const self = this;
|
this.set("retrying", true);
|
||||||
self.set('retrying', true);
|
const rollback = () => this.set("retrying", false);
|
||||||
this.get('model.postStream').refresh().then(function() {
|
this.get("model.postStream").refresh().then(rollback, rollback);
|
||||||
self.set('retrying', false);
|
|
||||||
}, function() {
|
|
||||||
self.set('retrying', false);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleWiki(post) {
|
toggleWiki(post) {
|
||||||
|
@ -692,11 +689,6 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
return post.unhide();
|
return post.unhide();
|
||||||
},
|
},
|
||||||
|
|
||||||
changePostOwner(post) {
|
|
||||||
this.get('selectedPosts').addObject(post);
|
|
||||||
this.send('changeOwner');
|
|
||||||
},
|
|
||||||
|
|
||||||
convertToPublicTopic() {
|
convertToPublicTopic() {
|
||||||
this.get('content').convertTopic("public");
|
this.get('content').convertTopic("public");
|
||||||
},
|
},
|
||||||
|
@ -742,99 +734,84 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
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'),
|
|
||||||
|
|
||||||
@computed('selectedPosts', 'selectedPostsCount', 'selectedPostsUsername')
|
|
||||||
canMergePosts(selectedPosts, selectedPostsCount, selectedPostsUsername) {
|
|
||||||
if (selectedPostsCount < 2) return false;
|
|
||||||
if (!selectedPosts.every(p => p.get('can_delete'))) return false;
|
|
||||||
return selectedPostsUsername !== undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
categories: Ember.computed.alias('site.categoriesList'),
|
|
||||||
|
|
||||||
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'),
|
hasError: Ember.computed.or('model.notFoundHtml', 'model.message'),
|
||||||
noErrorYet: Ember.computed.not('hasError'),
|
noErrorYet: Ember.computed.not('hasError'),
|
||||||
|
|
||||||
multiSelectChanged: function() {
|
categories: Ember.computed.alias('site.categoriesList'),
|
||||||
// Deselect all posts when multi select is turned off
|
|
||||||
if (!this.get('multiSelect')) {
|
|
||||||
this.send('deselectAll');
|
|
||||||
}
|
|
||||||
}.observes('multiSelect'),
|
|
||||||
|
|
||||||
deselectPost(post) {
|
selectedPostsCount: Ember.computed.alias('selectedPostIds.length'),
|
||||||
this.get('selectedPosts').removeObject(post);
|
|
||||||
|
|
||||||
const selectedReplies = this.get('selectedReplies');
|
@computed('selectedPostIds', 'model.postStream.posts', 'selectedPostIds.[]', 'model.postStream.posts.[]')
|
||||||
selectedReplies.removeObject(post);
|
selectedPosts(selectedPostIds, loadedPosts) {
|
||||||
|
return selectedPostIds.map(id => loadedPosts.find(p => p.id === id))
|
||||||
|
.filter(post => post !== undefined);
|
||||||
|
},
|
||||||
|
|
||||||
const selectedReply = selectedReplies.findBy('post_number', post.get('reply_to_post_number'));
|
@computed('selectedPostsCount', 'selectedPosts', 'selectedPosts.[]')
|
||||||
if (selectedReply) { selectedReplies.removeObject(selectedReply); }
|
selectedPostsUsername(selectedPostsCount, selectedPosts) {
|
||||||
|
if (selectedPosts.length < 1 || selectedPostsCount > selectedPosts.length) { return undefined; }
|
||||||
|
const username = selectedPosts[0].username;
|
||||||
|
return selectedPosts.every(p => p.username === username) ? username : undefined;
|
||||||
|
},
|
||||||
|
|
||||||
this.set('allPostsSelected', false);
|
@computed('selectedPostsCount', 'model.postStream.stream.length')
|
||||||
|
selectedAllPosts(selectedPostsCount, postsCount) {
|
||||||
|
return selectedPostsCount >= postsCount;
|
||||||
|
},
|
||||||
|
|
||||||
|
canSelectAll: Ember.computed.not('selectedAllPosts'),
|
||||||
|
canDeselectAll: Ember.computed.alias('selectedAllPosts'),
|
||||||
|
|
||||||
|
@computed('selectedPostsCount', 'selectedAllPosts', 'selectedPosts', 'selectedPosts.[]')
|
||||||
|
canDeleteSelected(selectedPostsCount, selectedAllPosts, selectedPosts) {
|
||||||
|
return selectedPostsCount > 0 && (selectedAllPosts || selectedPosts.every(p => p.can_delete));
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('canMergeTopic', 'selectedAllPosts')
|
||||||
|
canSplitTopic(canMergeTopic, selectedAllPosts) {
|
||||||
|
return canMergeTopic && !selectedAllPosts;
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('model.details.can_move_posts', 'selectedPostsCount')
|
||||||
|
canMergeTopic(canMovePosts, selectedPostsCount) {
|
||||||
|
return canMovePosts && selectedPostsCount > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('currentUser.admin', 'selectedPostsCount', 'selectedPostsUsername')
|
||||||
|
canChangeOwner(isAdmin, selectedPostsCount, selectedPostsUsername) {
|
||||||
|
return isAdmin && selectedPostsCount > 0 && selectedPostsUsername !== undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('selectedPostsCount', 'selectedPostsUsername', 'selectedPosts', 'selectedPosts.[]')
|
||||||
|
canMergePosts(selectedPostsCount, selectedPostsUsername, selectedPosts) {
|
||||||
|
return selectedPostsCount > 1 &&
|
||||||
|
selectedPostsUsername !== undefined &&
|
||||||
|
selectedPosts.every(p => p.can_delete);
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes("multiSelect")
|
||||||
|
_multiSelectChanged() {
|
||||||
|
this.set('selectedPostIds', []);
|
||||||
},
|
},
|
||||||
|
|
||||||
postSelected(post) {
|
postSelected(post) {
|
||||||
if (this.get('allPostsSelected')) { return true; }
|
return this.get('selectedAllPost') || this.get('selectedPostIds').includes(post.id);
|
||||||
if (this.get('selectedPosts').includes(post)) { return true; }
|
|
||||||
if (this.get('selectedReplies').findBy('post_number', post.get('reply_to_post_number'))) { return true; }
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
loadingHTML: function() {
|
@computed
|
||||||
|
loadingHTML() {
|
||||||
return spinnerHTML;
|
return spinnerHTML;
|
||||||
}.property(),
|
},
|
||||||
|
|
||||||
recoverTopic() {
|
recoverTopic() {
|
||||||
this.get('content').recover();
|
this.get('content').recover();
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTopic() {
|
deleteTopic() {
|
||||||
this.get('content').destroy(Discourse.User.current());
|
this.get('content').destroy(this.currentUser);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Receive notifications for this topic
|
|
||||||
subscribe() {
|
subscribe() {
|
||||||
// Unsubscribe before subscribing again
|
|
||||||
this.unsubscribe();
|
this.unsubscribe();
|
||||||
|
|
||||||
const refresh = (args) => this.appEvents.trigger('post-stream:refresh', args);
|
const refresh = (args) => this.appEvents.trigger('post-stream:refresh', args);
|
||||||
|
@ -929,38 +906,22 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
}, 500),
|
}, 500),
|
||||||
|
|
||||||
unsubscribe() {
|
unsubscribe() {
|
||||||
const topicId = this.get('content.id');
|
// never unsubscribe when navigating from topic to topic
|
||||||
if (!topicId) return;
|
if (!this.get("content.id")) return;
|
||||||
|
|
||||||
// there is a condition where the view never calls unsubscribe, navigate to a topic from a topic
|
|
||||||
this.messageBus.unsubscribe('/topic/*');
|
this.messageBus.unsubscribe('/topic/*');
|
||||||
},
|
},
|
||||||
|
|
||||||
// Topic related
|
|
||||||
reply() {
|
reply() {
|
||||||
this.replyToPost();
|
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
readPosts(topicId, postNumbers) {
|
readPosts(topicId, postNumbers) {
|
||||||
const topic = this.get("model");
|
const topic = this.get("model");
|
||||||
const postStream = topic.get("postStream");
|
const postStream = topic.get("postStream");
|
||||||
|
|
||||||
if (topic.get('id') === topicId) {
|
if (topic.get('id') === topicId) {
|
||||||
postStream.get('posts').forEach(post => {
|
postStream.get('posts').forEach(post => {
|
||||||
if (!post.read && postNumbers.indexOf(post.post_number) !== -1) {
|
if (!post.read && postNumbers.includes(post.post_number)) {
|
||||||
post.set('read', true);
|
post.set('read', true);
|
||||||
this.appEvents.trigger('post-stream:refresh', { id: post.get('id') });
|
this.appEvents.trigger('post-stream:refresh', { id: post.get('id') });
|
||||||
}
|
}
|
||||||
|
@ -973,16 +934,17 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
// automatically unpin topics when the user reaches the bottom
|
// automatically unpin topics when the user reaches the bottom
|
||||||
const max = _.max(postNumbers);
|
const max = _.max(postNumbers);
|
||||||
if (topic.get("pinned") && max >= topic.get("highest_post_number")) {
|
if (topic.get("pinned") && max >= topic.get("highest_post_number")) {
|
||||||
Em.run.next(() => topic.clearPin());
|
Ember.run.next(() => topic.clearPin());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_showFooter: function() {
|
@observes("model.postStream.loaded", "model.postStream.loadedAllPosts")
|
||||||
|
_showFooter() {
|
||||||
const showFooter = this.get("model.postStream.loaded") && this.get("model.postStream.loadedAllPosts");
|
const showFooter = this.get("model.postStream.loaded") && this.get("model.postStream.loadedAllPosts");
|
||||||
this.set("application.showFooter", showFooter);
|
this.set("application.showFooter", showFooter);
|
||||||
}.observes("model.postStream.{loaded,loadedAllPosts}")
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
export default Em.Mixin.create({
|
|
||||||
|
|
||||||
selectedPostsCount: function() {
|
|
||||||
if (this.get('allPostsSelected')) {
|
|
||||||
return this.get('model.posts_count') || this.get('topic.posts_count') || this.get('posts_count');
|
|
||||||
}
|
|
||||||
|
|
||||||
var sum = this.get('selectedPosts.length') || 0;
|
|
||||||
if (this.get('selectedReplies')) {
|
|
||||||
this.get('selectedReplies').forEach(function (p) {
|
|
||||||
sum += p.get('reply_count') || 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum;
|
|
||||||
}.property('selectedPosts.length', 'allPostsSelected', 'selectedReplies.length'),
|
|
||||||
|
|
||||||
// The username that owns every selected post, or undefined if no selection or if ownership is mixed.
|
|
||||||
selectedPostsUsername: function() {
|
|
||||||
// Don't proceed if replies are selected or usernames are mixed
|
|
||||||
// Changing ownership in those cases normally doesn't make sense
|
|
||||||
if (this.get('selectedReplies') && this.get('selectedReplies').length > 0) { return undefined; }
|
|
||||||
if (this.get('selectedPosts').length <= 0) { return undefined; }
|
|
||||||
|
|
||||||
const selectedPosts = this.get('selectedPosts'),
|
|
||||||
username = selectedPosts[0].username;
|
|
||||||
|
|
||||||
if (selectedPosts.every(function(post) { return post.username === username; })) {
|
|
||||||
return username;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}.property('selectedPosts.length', 'selectedReplies.length')
|
|
||||||
});
|
|
|
@ -538,7 +538,7 @@ export default RestModel.extend({
|
||||||
triggerDeletedPost(postId){
|
triggerDeletedPost(postId){
|
||||||
const existing = this._identityMap[postId];
|
const existing = this._identityMap[postId];
|
||||||
|
|
||||||
if (existing) {
|
if (existing && !existing.deleted_at) {
|
||||||
const url = "/posts/" + postId;
|
const url = "/posts/" + postId;
|
||||||
const store = this.store;
|
const store = this.store;
|
||||||
|
|
||||||
|
|
|
@ -329,22 +329,17 @@ Post.reopenClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteMany(selectedPosts, selectedReplies) {
|
deleteMany(post_ids) {
|
||||||
return ajax("/posts/destroy_many", {
|
return ajax("/posts/destroy_many", {
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
data: {
|
data: { post_ids }
|
||||||
post_ids: selectedPosts.map(function(p) { return p.get('id'); }),
|
|
||||||
reply_post_ids: selectedReplies.map(function(p) { return p.get('id'); })
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
mergePosts(selectedPosts) {
|
mergePosts(post_ids) {
|
||||||
return ajax("/posts/merge_posts", {
|
return ajax("/posts/merge_posts", {
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
data: { post_ids: selectedPosts.map(p => p.get('id')) }
|
data: { post_ids }
|
||||||
}).catch(() => {
|
|
||||||
self.flash(I18n.t('topic.merge_posts.error'));
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -181,8 +181,9 @@
|
||||||
currentPostChanged=(action "currentPostChanged")
|
currentPostChanged=(action "currentPostChanged")
|
||||||
currentPostScrolled=(action "currentPostScrolled")
|
currentPostScrolled=(action "currentPostScrolled")
|
||||||
bottomVisibleChanged=(action "bottomVisibleChanged")
|
bottomVisibleChanged=(action "bottomVisibleChanged")
|
||||||
selectPost=(action "toggledSelectedPost")
|
togglePostSelection=(action "togglePostSelection")
|
||||||
selectReplies=(action "toggledSelectedPostReplies")
|
selectReplies=(action "selectReplies")
|
||||||
|
selectBelow=(action "selectBelow")
|
||||||
fillGapBefore=(action "fillGapBefore")
|
fillGapBefore=(action "fillGapBefore")
|
||||||
fillGapAfter=(action "fillGapAfter")}}
|
fillGapAfter=(action "fillGapAfter")}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
|
@ -87,7 +87,6 @@ export default createWidget('post-stream', {
|
||||||
|
|
||||||
if (attrs.multiSelect) {
|
if (attrs.multiSelect) {
|
||||||
transformed.selected = attrs.selectedQuery(post);
|
transformed.selected = attrs.selectedQuery(post);
|
||||||
transformed.selectedPostsCount = attrs.selectedPostsCount;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,15 +37,31 @@ createWidget('select-post', {
|
||||||
html(attrs) {
|
html(attrs) {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
if (attrs.replyCount > 0 && !attrs.selected) {
|
if (!attrs.selected && attrs.post_number > 1) {
|
||||||
buttons.push(this.attach('button', { label: 'topic.multi_select.select_replies', action: 'selectReplies' }));
|
if (attrs.replyCount > 0) {
|
||||||
|
buttons.push(this.attach('button', {
|
||||||
|
label: 'topic.multi_select.select_replies.label',
|
||||||
|
title: 'topic.multi_select.select_replies.title',
|
||||||
|
action: 'selectReplies',
|
||||||
|
className: 'select-replies'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
buttons.push(this.attach('button', {
|
||||||
|
label: 'topic.multi_select.select_below.label',
|
||||||
|
title: 'topic.multi_select.select_below.title',
|
||||||
|
action: 'selectBelow',
|
||||||
|
className: 'select-below'
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectPostKey = attrs.selected ? 'topic.multi_select.selected' : 'topic.multi_select.select';
|
const key = `topic.multi_select.${attrs.selected ? 'selected' : 'select' }_post`;
|
||||||
buttons.push(this.attach('button', { className: 'select-post',
|
buttons.push(this.attach('button', {
|
||||||
label: selectPostKey,
|
label: key + ".label",
|
||||||
labelOptions: { count: attrs.selectedPostsCount },
|
title: key + ".title",
|
||||||
action: 'selectPost' }));
|
action: 'togglePostSelection',
|
||||||
|
className: 'select-post'
|
||||||
|
}));
|
||||||
|
|
||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,8 +7,7 @@ require_dependency 'new_post_result_serializer'
|
||||||
|
|
||||||
class PostsController < ApplicationController
|
class PostsController < ApplicationController
|
||||||
|
|
||||||
# Need to be logged in for all actions here
|
before_action :ensure_logged_in, except: [:show, :replies, :by_number, :short_link, :reply_history, :replyIids, :revisions, :latest_revision, :expand_embed, :markdown_id, :markdown_num, :cooked, :latest, :user_posts_feed]
|
||||||
before_action :ensure_logged_in, except: [:show, :replies, :by_number, :short_link, :reply_history, :revisions, :latest_revision, :expand_embed, :markdown_id, :markdown_num, :cooked, :latest, :user_posts_feed]
|
|
||||||
|
|
||||||
skip_before_action :preload_json, :check_xhr, only: [:markdown_id, :markdown_num, :short_link, :latest, :user_posts_feed]
|
skip_before_action :preload_json, :check_xhr, only: [:markdown_id, :markdown_num, :short_link, :latest, :user_posts_feed]
|
||||||
|
|
||||||
|
@ -224,6 +223,11 @@ class PostsController < ApplicationController
|
||||||
render_serialized(post.reply_history(params[:max_replies].to_i, guardian), PostSerializer)
|
render_serialized(post.reply_history(params[:max_replies].to_i, guardian), PostSerializer)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reply_ids
|
||||||
|
post = find_post_from_params
|
||||||
|
render json: post.reply_ids(guardian).to_json
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
post = find_post_from_params
|
post = find_post_from_params
|
||||||
RateLimiter.new(current_user, "delete_post", 3, 1.minute).performed! unless current_user.staff?
|
RateLimiter.new(current_user, "delete_post", 3, 1.minute).performed! unless current_user.staff?
|
||||||
|
|
|
@ -654,6 +654,32 @@ class Post < ActiveRecord::Base
|
||||||
Post.secured(guardian).where(id: post_ids).includes(:user, :topic).order(:id).to_a
|
Post.secured(guardian).where(id: post_ids).includes(:user, :topic).order(:id).to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reply_ids(guardian = nil)
|
||||||
|
replies = Post.exec_sql("
|
||||||
|
WITH RECURSIVE breadcrumb(id, post_number, level) AS (
|
||||||
|
SELECT id, post_number, 0
|
||||||
|
FROM posts
|
||||||
|
WHERE id = :post_id
|
||||||
|
UNION
|
||||||
|
SELECT p.id, p.post_number, b.level + 1
|
||||||
|
FROM posts p, breadcrumb b
|
||||||
|
WHERE b.post_number = p.reply_to_post_number
|
||||||
|
AND p.topic_id = :topic_id
|
||||||
|
), breadcrumb_with_replies AS (
|
||||||
|
SELECT b.id, b.level, COUNT(*)
|
||||||
|
FROM breadcrumb b, post_replies pr
|
||||||
|
WHERE pr.reply_id = b.id
|
||||||
|
GROUP BY b.id, b.level
|
||||||
|
) SELECT id, level FROM breadcrumb_with_replies WHERE count = 1 ORDER BY id
|
||||||
|
", post_id: id, topic_id: topic_id).to_a
|
||||||
|
|
||||||
|
replies.map! { |r| { id: r["id"].to_i, level: r["level"].to_i } }
|
||||||
|
|
||||||
|
secured_ids = Post.secured(guardian).where(id: replies.map { |r| r[:id] }).pluck(:id).to_set
|
||||||
|
|
||||||
|
replies.reject { |r| r[:id] == id || !secured_ids.include?(r[:id]) }
|
||||||
|
end
|
||||||
|
|
||||||
def revert_to(number)
|
def revert_to(number)
|
||||||
return if number >= version
|
return if number >= version
|
||||||
post_revision = PostRevision.find_by(post_id: id, number: (number + 1))
|
post_revision = PostRevision.find_by(post_id: id, number: (number + 1))
|
||||||
|
|
|
@ -1858,7 +1858,18 @@ en:
|
||||||
multi_select:
|
multi_select:
|
||||||
select: 'select'
|
select: 'select'
|
||||||
selected: 'selected ({{count}})'
|
selected: 'selected ({{count}})'
|
||||||
select_replies: 'select +replies'
|
select_post:
|
||||||
|
label: 'select'
|
||||||
|
title: 'Add post to selection'
|
||||||
|
selected_post:
|
||||||
|
label: 'selected'
|
||||||
|
title: 'Click to remove post from selection'
|
||||||
|
select_replies:
|
||||||
|
label: 'select +replies'
|
||||||
|
title: 'Add post and all its replies to selection'
|
||||||
|
select_below:
|
||||||
|
label: 'select +below'
|
||||||
|
title: 'Add post and all after it to selection'
|
||||||
delete: delete selected
|
delete: delete selected
|
||||||
cancel: cancel selecting
|
cancel: cancel selecting
|
||||||
select_all: select all
|
select_all: select all
|
||||||
|
@ -1950,11 +1961,14 @@ en:
|
||||||
share: "share a link to this post"
|
share: "share a link to this post"
|
||||||
more: "More"
|
more: "More"
|
||||||
delete_replies:
|
delete_replies:
|
||||||
confirm:
|
confirm: "Do you also want to delete the replies to this post?"
|
||||||
one: "Do you also want to delete the direct reply to this post?"
|
direct_replies:
|
||||||
other: "Do you also want to delete the {{count}} direct replies to this post?"
|
one: "Yes, and 1 direct reply"
|
||||||
yes_value: "Yes, delete the replies too"
|
other: "Yes, and {{count}} direct replies"
|
||||||
no_value: "No, just this post"
|
all_replies:
|
||||||
|
one: "Yes, and 1 reply"
|
||||||
|
other: "Yes, and all {{count}} replies"
|
||||||
|
just_the_post: "No, just this post"
|
||||||
admin: "post admin actions"
|
admin: "post admin actions"
|
||||||
wiki: "Make Wiki"
|
wiki: "Make Wiki"
|
||||||
unwiki: "Remove Wiki"
|
unwiki: "Remove Wiki"
|
||||||
|
@ -2051,11 +2065,10 @@ en:
|
||||||
delete:
|
delete:
|
||||||
confirm:
|
confirm:
|
||||||
one: "Are you sure you want to delete that post?"
|
one: "Are you sure you want to delete that post?"
|
||||||
other: "Are you sure you want to delete all those posts?"
|
other: "Are you sure you want to delete those {{count}} posts?"
|
||||||
|
|
||||||
merge:
|
merge:
|
||||||
confirm:
|
confirm:
|
||||||
one: "Are you sure you want to merge those posts?"
|
|
||||||
other: "Are you sure you want to merge those {{count}} posts?"
|
other: "Are you sure you want to merge those {{count}} posts?"
|
||||||
|
|
||||||
revisions:
|
revisions:
|
||||||
|
|
|
@ -434,6 +434,7 @@ Discourse::Application.routes.draw do
|
||||||
get "private-posts" => "posts#latest", id: "private_posts"
|
get "private-posts" => "posts#latest", id: "private_posts"
|
||||||
get "posts/by_number/:topic_id/:post_number" => "posts#by_number"
|
get "posts/by_number/:topic_id/:post_number" => "posts#by_number"
|
||||||
get "posts/:id/reply-history" => "posts#reply_history"
|
get "posts/:id/reply-history" => "posts#reply_history"
|
||||||
|
get "posts/:id/reply-ids" => "posts#reply_ids"
|
||||||
get "posts/:username/deleted" => "posts#deleted_posts", constraints: { username: USERNAME_ROUTE_FORMAT }
|
get "posts/:username/deleted" => "posts#deleted_posts", constraints: { username: USERNAME_ROUTE_FORMAT }
|
||||||
get "posts/:username/flagged" => "posts#flagged_posts", constraints: { username: USERNAME_ROUTE_FORMAT }
|
get "posts/:username/flagged" => "posts#flagged_posts", constraints: { username: USERNAME_ROUTE_FORMAT }
|
||||||
|
|
||||||
|
|
|
@ -777,6 +777,32 @@ describe Post do
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "reply_ids" do
|
||||||
|
|
||||||
|
let!(:topic) { Fabricate(:topic) }
|
||||||
|
let!(:p1) { Fabricate(:post, topic: topic, post_number: 1) }
|
||||||
|
let!(:p2) { Fabricate(:post, topic: topic, post_number: 2, reply_to_post_number: 1) }
|
||||||
|
let!(:p3) { Fabricate(:post, topic: topic, post_number: 3) }
|
||||||
|
let!(:p4) { Fabricate(:post, topic: topic, post_number: 4, reply_to_post_number: 2) }
|
||||||
|
let!(:p5) { Fabricate(:post, topic: topic, post_number: 5, reply_to_post_number: 4) }
|
||||||
|
|
||||||
|
before {
|
||||||
|
PostReply.create!(post: p1, reply: p2)
|
||||||
|
PostReply.create!(post: p2, reply: p4)
|
||||||
|
PostReply.create!(post: p3, reply: p5)
|
||||||
|
PostReply.create!(post: p4, reply: p5)
|
||||||
|
}
|
||||||
|
|
||||||
|
it "returns the reply ids and their level" do
|
||||||
|
expect(p1.reply_ids).to eq([{ id: p2.id, level: 1 }, { id: p4.id, level: 2 }])
|
||||||
|
expect(p2.reply_ids).to eq([{ id: p4.id, level: 1 }])
|
||||||
|
expect(p3.reply_ids).to be_empty # no replies
|
||||||
|
expect(p4.reply_ids).to be_empty # not a direct reply
|
||||||
|
expect(p5.reply_ids).to be_empty # last post
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
describe 'urls' do
|
describe 'urls' do
|
||||||
it 'no-ops for empty list' do
|
it 'no-ops for empty list' do
|
||||||
expect(Post.urls([])).to eq({})
|
expect(Post.urls([])).to eq({})
|
||||||
|
|
|
@ -1,126 +1,318 @@
|
||||||
import { mapRoutes } from 'discourse/mapping-router';
|
import AppEvents from "discourse/lib/app-events";
|
||||||
|
import Topic from "discourse/models/topic";
|
||||||
|
|
||||||
moduleFor('controller:topic', 'controller:topic', {
|
moduleFor("controller:topic", "controller:topic", {
|
||||||
needs: ['controller:modal', 'controller:composer', 'controller:application'],
|
needs: ["controller:composer", "controller:application"],
|
||||||
beforeEach() {
|
beforeEach() {
|
||||||
this.registry.register('router:main', mapRoutes());
|
this.registry.register("app-events:main", AppEvents.create(), { instantiate: false });
|
||||||
},
|
this.registry.injection("controller", "appEvents", "app-events:main");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
import Topic from 'discourse/models/topic';
|
QUnit.test("editTopic", function(assert) {
|
||||||
import AppEvents from 'discourse/lib/app-events';
|
const model = Topic.create();
|
||||||
|
const controller = this.subject({ model });
|
||||||
|
|
||||||
var buildTopic = function() {
|
assert.not(controller.get("editingTopic"), "we are not editing by default");
|
||||||
return Topic.create({
|
|
||||||
title: "Qunit Test Topic",
|
|
||||||
participants: [
|
|
||||||
{id: 1234,
|
|
||||||
post_count: 4,
|
|
||||||
username: "eviltrout"}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
controller.set("model.details.can_edit", false);
|
||||||
|
controller.send("editTopic");
|
||||||
|
|
||||||
QUnit.test("editingMode", function(assert) {
|
assert.not(controller.get("editingTopic"), "calling editTopic doesn't enable editing unless the user can edit");
|
||||||
var topic = buildTopic(),
|
|
||||||
topicController = this.subject({model: topic});
|
|
||||||
|
|
||||||
assert.ok(!topicController.get('editingTopic'), "we are not editing by default");
|
controller.set("model.details.can_edit", true);
|
||||||
|
controller.send("editTopic");
|
||||||
|
|
||||||
topicController.set('model.details.can_edit', false);
|
assert.ok(controller.get("editingTopic"), "calling editTopic enables editing if the user can edit");
|
||||||
topicController.send('editTopic');
|
assert.equal(controller.get("buffered.title"), model.get("title"));
|
||||||
assert.ok(!topicController.get('editingTopic'), "calling editTopic doesn't enable editing unless the user can edit");
|
assert.equal(controller.get("buffered.category_id"), model.get("category_id"));
|
||||||
|
|
||||||
topicController.set('model.details.can_edit', true);
|
controller.send("cancelEditingTopic");
|
||||||
topicController.send('editTopic');
|
|
||||||
assert.ok(topicController.get('editingTopic'), "calling editTopic enables editing if the user can edit");
|
|
||||||
assert.equal(topicController.get('buffered.title'), topic.get('title'));
|
|
||||||
assert.equal(topicController.get('buffered.category_id'), topic.get('category_id'));
|
|
||||||
|
|
||||||
topicController.send('cancelEditingTopic');
|
assert.not(controller.get("editingTopic"), "cancelling edit mode reverts the property value");
|
||||||
assert.ok(!topicController.get('editingTopic'), "cancelling edit mode reverts the property value");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("toggledSelectedPost", function(assert) {
|
QUnit.test("toggleMultiSelect", function(assert) {
|
||||||
var tc = this.subject({ model: buildTopic() }),
|
const model = Topic.create();
|
||||||
post = Discourse.Post.create({id: 123, post_number: 2}),
|
const controller = this.subject({ model });
|
||||||
postStream = tc.get('model.postStream');
|
|
||||||
|
|
||||||
postStream.appendPost(post);
|
assert.not(controller.get("multiSelect"), "multi selection mode is disabled by default");
|
||||||
postStream.appendPost(Discourse.Post.create({id: 124, post_number: 3}));
|
|
||||||
|
|
||||||
assert.blank(tc.get('selectedPosts'), "there are no selected posts by default");
|
controller.get("selectedPostIds").pushObject(1);
|
||||||
assert.equal(tc.get('selectedPostsCount'), 0, "there is a selected post count of 0");
|
assert.equal(controller.get("selectedPostIds.length"), 1);
|
||||||
assert.ok(!tc.postSelected(post), "the post is not selected by default");
|
|
||||||
|
|
||||||
tc.send('toggledSelectedPost', post);
|
controller.send("toggleMultiSelect");
|
||||||
assert.present(tc.get('selectedPosts'), "there is a selectedPosts collection");
|
|
||||||
assert.equal(tc.get('selectedPostsCount'), 1, "there is a selected post now");
|
|
||||||
assert.ok(tc.postSelected(post), "the post is now selected");
|
|
||||||
|
|
||||||
tc.send('toggledSelectedPost', post);
|
assert.ok(controller.get("multiSelect"), "calling 'toggleMultiSelect' once enables multi selection mode");
|
||||||
assert.ok(!tc.postSelected(post), "the post is no longer selected");
|
assert.equal(controller.get("selectedPostIds.length"), 0, "toggling 'multiSelect' clears 'selectedPostIds'");
|
||||||
|
|
||||||
|
controller.get("selectedPostIds").pushObject(2);
|
||||||
|
assert.equal(controller.get("selectedPostIds.length"), 1);
|
||||||
|
|
||||||
|
controller.send("toggleMultiSelect");
|
||||||
|
|
||||||
|
assert.not(controller.get("multiSelect"), "calling 'toggleMultiSelect' twice disables multi selection mode");
|
||||||
|
assert.equal(controller.get("selectedPostIds.length"), 0, "toggling 'multiSelect' clears 'selectedPostIds'");
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("selectAll", function(assert) {
|
QUnit.test("selectedPosts", function(assert) {
|
||||||
var tc = this.subject({model: buildTopic(), appEvents: AppEvents.create()}),
|
const postStream = { posts: [{ id: 1 }, { id: 2 }, { id: 3 }] };
|
||||||
post = Discourse.Post.create({id: 123, post_number: 2}),
|
const model = Topic.create({ postStream });
|
||||||
postStream = tc.get('model.postStream');
|
const controller = this.subject({ model });
|
||||||
|
|
||||||
postStream.appendPost(post);
|
controller.set("selectedPostIds", [1, 2, 42]);
|
||||||
|
|
||||||
assert.ok(!tc.postSelected(post), "the post is not selected by default");
|
|
||||||
tc.send('selectAll');
|
|
||||||
assert.ok(tc.postSelected(post), "the post is now selected");
|
|
||||||
assert.ok(tc.get('allPostsSelected'), "all posts are selected");
|
|
||||||
tc.send('deselectAll');
|
|
||||||
assert.ok(!tc.postSelected(post), "the post is deselected again");
|
|
||||||
assert.ok(!tc.get('allPostsSelected'), "all posts are not selected");
|
|
||||||
|
|
||||||
|
assert.equal(controller.get("selectedPosts.length"), 2, "selectedPosts only contains already loaded posts");
|
||||||
|
assert.not(controller.get("selectedPosts").some(p => p === undefined), "selectedPosts only contains valid post objects");
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("Automating setting of allPostsSelected", function(assert) {
|
QUnit.test("selectedAllPosts", function(assert) {
|
||||||
var topic = buildTopic(),
|
const postStream = { stream: [1, 2, 3] };
|
||||||
tc = this.subject({model: topic}),
|
const model = Topic.create({ postStream });
|
||||||
post = Discourse.Post.create({id: 123, post_number: 2}),
|
const controller = this.subject({ model });
|
||||||
postStream = tc.get('model.postStream');
|
|
||||||
|
|
||||||
topic.set('posts_count', 1);
|
controller.set("selectedPostIds", [1, 2]);
|
||||||
postStream.appendPost(post);
|
|
||||||
assert.ok(!tc.get('allPostsSelected'), "all posts are not selected by default");
|
|
||||||
|
|
||||||
tc.send('toggledSelectedPost', post);
|
assert.not(controller.get("selectedAllPosts"), "not all posts are selected");
|
||||||
assert.ok(tc.get('allPostsSelected'), "all posts are selected if we select the only post");
|
|
||||||
|
|
||||||
tc.send('toggledSelectedPost', post);
|
controller.get("selectedPostIds").pushObject(3);
|
||||||
assert.ok(!tc.get('allPostsSelected'), "the posts are no longer automatically selected");
|
|
||||||
|
assert.ok(controller.get("selectedAllPosts"), "all posts are selected");
|
||||||
|
|
||||||
|
controller.get("selectedPostIds").pushObject(42);
|
||||||
|
|
||||||
|
assert.ok(controller.get("selectedAllPosts"), "all posts (including filtered posts) are selected");
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("Select Replies when present", function(assert) {
|
QUnit.test("selectedPostsUsername", function(assert) {
|
||||||
var topic = buildTopic(),
|
const postStream = {
|
||||||
tc = this.subject({ model: topic, appEvents: AppEvents.create() }),
|
posts: [
|
||||||
p1 = Discourse.Post.create({id: 1, post_number: 1, reply_count: 1}),
|
{ id: 1, username: "gary" },
|
||||||
p2 = Discourse.Post.create({id: 2, post_number: 2}),
|
{ id: 2, username: "gary" },
|
||||||
p3 = Discourse.Post.create({id: 2, post_number: 3, reply_to_post_number: 1});
|
{ id: 3, username: "lili" },
|
||||||
|
],
|
||||||
|
stream: [1, 2, 3]
|
||||||
|
};
|
||||||
|
|
||||||
assert.ok(!tc.postSelected(p3), "replies are not selected by default");
|
const model = Topic.create({ postStream });
|
||||||
tc.send('toggledSelectedPostReplies', p1);
|
const controller = this.subject({ model });
|
||||||
assert.ok(tc.postSelected(p1), "it selects the post");
|
const selectedPostIds = controller.get("selectedPostIds");
|
||||||
assert.ok(!tc.postSelected(p2), "it doesn't select a post that's not a reply");
|
|
||||||
assert.ok(tc.postSelected(p3), "it selects a post that is a reply");
|
|
||||||
assert.equal(tc.get('selectedPostsCount'), 2, "it has a selected posts count of two");
|
|
||||||
|
|
||||||
// If we deselected the post whose replies are selected...
|
assert.equal(controller.get("selectedPostsUsername"), undefined, "no username when no selected posts");
|
||||||
tc.send('toggledSelectedPost', p1);
|
|
||||||
assert.ok(!tc.postSelected(p1), "it deselects the post");
|
|
||||||
assert.ok(!tc.postSelected(p3), "it deselects the replies too");
|
|
||||||
|
|
||||||
// If we deselect a reply, it should deselect the parent's replies selected attribute. Weird but what else would make sense?
|
selectedPostIds.pushObject(1);
|
||||||
tc.send('toggledSelectedPostReplies', p1);
|
|
||||||
tc.send('toggledSelectedPost', p3);
|
|
||||||
assert.ok(tc.postSelected(p1), "the post stays selected");
|
|
||||||
assert.ok(!tc.postSelected(p3), "it deselects the replies too");
|
|
||||||
|
|
||||||
|
assert.equal(controller.get("selectedPostsUsername"), "gary", "username of the selected posts");
|
||||||
|
|
||||||
|
selectedPostIds.pushObject(2);
|
||||||
|
|
||||||
|
assert.equal(controller.get("selectedPostsUsername"), "gary", "username of all the selected posts when same user");
|
||||||
|
|
||||||
|
selectedPostIds.pushObject(3);
|
||||||
|
|
||||||
|
assert.equal(controller.get("selectedPostsUsername"), undefined, "no username when more than 1 user");
|
||||||
|
|
||||||
|
selectedPostIds.replace(2, 1, [42]);
|
||||||
|
|
||||||
|
assert.equal(controller.get("selectedPostsUsername"), undefined, "no username when not already loaded posts are selected");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test("showSelectedPostsAtBottom", function(assert) {
|
||||||
|
const site = Ember.Object.create({ mobileView: false });
|
||||||
|
const model = Topic.create({ posts_count: 3 });
|
||||||
|
const controller = this.subject({ model, site });
|
||||||
|
|
||||||
|
assert.not(controller.get("showSelectedPostsAtBottom"), "false on desktop")
|
||||||
|
|
||||||
|
site.set("mobileView", true);
|
||||||
|
|
||||||
|
assert.not(controller.get("showSelectedPostsAtBottom"), "requires at least 3 posts on mobile");
|
||||||
|
|
||||||
|
model.set("posts_count", 4);
|
||||||
|
|
||||||
|
assert.ok(controller.get("showSelectedPostsAtBottom"), "true when mobile and more than 3 posts");
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("canDeleteSelected", function(assert) {
|
||||||
|
const postStream = {
|
||||||
|
posts: [
|
||||||
|
{ id: 1, can_delete: false },
|
||||||
|
{ id: 2, can_delete: true },
|
||||||
|
{ id: 3, can_delete: true }
|
||||||
|
],
|
||||||
|
stream: [1, 2, 3]
|
||||||
|
};
|
||||||
|
|
||||||
|
const model = Topic.create({ postStream });
|
||||||
|
const controller = this.subject({ model });
|
||||||
|
const selectedPostIds = controller.get("selectedPostIds");
|
||||||
|
|
||||||
|
assert.not(controller.get("canDeleteSelected"), "false when no posts are selected");
|
||||||
|
|
||||||
|
selectedPostIds.pushObject(1);
|
||||||
|
|
||||||
|
assert.not(controller.get("canDeleteSelected"), "false when can't delete one of the selected posts");
|
||||||
|
|
||||||
|
selectedPostIds.replace(0, 1, [2, 3]);
|
||||||
|
|
||||||
|
assert.ok(controller.get("canDeleteSelected"), "true when all selected posts can be deleted");
|
||||||
|
|
||||||
|
selectedPostIds.pushObject(1);
|
||||||
|
|
||||||
|
assert.ok(controller.get("canDeleteSelected"), "true when all posts are selected");
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("Can split/merge topic", function(assert) {
|
||||||
|
const postStream = {
|
||||||
|
posts: [{ id: 1 }, { id: 2 }],
|
||||||
|
stream: [1, 2]
|
||||||
|
};
|
||||||
|
|
||||||
|
const model = Topic.create({ postStream, details: { can_move_posts: false } });
|
||||||
|
const controller = this.subject({ model });
|
||||||
|
const selectedPostIds = controller.get("selectedPostIds");
|
||||||
|
|
||||||
|
assert.not(controller.get("canSplitTopic"), "can't split topic when no posts are selected");
|
||||||
|
assert.not(controller.get("canMergeTopic"), "can't merge topic when no posts are selected");
|
||||||
|
|
||||||
|
selectedPostIds.pushObject(1);
|
||||||
|
|
||||||
|
assert.not(controller.get("canSplitTopic"), "can't split topic when can't move posts");
|
||||||
|
assert.not(controller.get("canMergeTopic"), "can't merge topic when can't move posts");
|
||||||
|
|
||||||
|
model.set("details.can_move_posts", true);
|
||||||
|
|
||||||
|
assert.ok(controller.get("canSplitTopic"), "can split topic");
|
||||||
|
assert.ok(controller.get("canMergeTopic"), "can merge topic");
|
||||||
|
|
||||||
|
selectedPostIds.pushObject(2);
|
||||||
|
|
||||||
|
assert.not(controller.get("canSplitTopic"), "can't split topic when all posts are selected");
|
||||||
|
assert.ok(controller.get("canMergeTopic"), "can merge topic when all posts are selected");
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("canChangeOwner", function(assert) {
|
||||||
|
const currentUser = Discourse.User.create({ admin: false });
|
||||||
|
this.registry.register("current-user:main", currentUser, { instantiate: false });
|
||||||
|
this.registry.injection("controller", "currentUser", "current-user:main");
|
||||||
|
|
||||||
|
const postStream = {
|
||||||
|
posts: [
|
||||||
|
{ id: 1, username: "gary" },
|
||||||
|
{ id: 2, username: "lili" },
|
||||||
|
],
|
||||||
|
stream: [1, 2]
|
||||||
|
};
|
||||||
|
|
||||||
|
const model = Topic.create({ postStream, currentUser: { admin: false }});
|
||||||
|
const controller = this.subject({ model });
|
||||||
|
const selectedPostIds = controller.get("selectedPostIds");
|
||||||
|
|
||||||
|
assert.not(controller.get("canChangeOwner"), "false when no posts are selected");
|
||||||
|
|
||||||
|
selectedPostIds.pushObject(1);
|
||||||
|
|
||||||
|
assert.not(controller.get("canChangeOwner"), "false when not admin");
|
||||||
|
|
||||||
|
currentUser.set("admin", true);
|
||||||
|
|
||||||
|
assert.ok(controller.get("canChangeOwner"), "true when admin and one post is selected");
|
||||||
|
|
||||||
|
selectedPostIds.pushObject(2);
|
||||||
|
|
||||||
|
assert.not(controller.get("canChangeOwner"), "false when admin but more than 1 user");
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("canMergePosts", function(assert) {
|
||||||
|
const postStream = {
|
||||||
|
posts: [
|
||||||
|
{ id: 1, username: "gary", can_delete: true },
|
||||||
|
{ id: 2, username: "lili", can_delete: true },
|
||||||
|
{ id: 3, username: "gary", can_delete: false },
|
||||||
|
{ id: 4, username: "gary", can_delete: true },
|
||||||
|
],
|
||||||
|
stream: [1, 2, 3]
|
||||||
|
};
|
||||||
|
|
||||||
|
const model = Topic.create({ postStream });
|
||||||
|
const controller = this.subject({ model });
|
||||||
|
const selectedPostIds = controller.get("selectedPostIds");
|
||||||
|
|
||||||
|
assert.not(controller.get("canMergePosts"), "false when no posts are selected");
|
||||||
|
|
||||||
|
selectedPostIds.pushObject(1);
|
||||||
|
|
||||||
|
assert.not(controller.get("canMergePosts"), "false when only one post is selected");
|
||||||
|
|
||||||
|
selectedPostIds.pushObject(2);
|
||||||
|
|
||||||
|
assert.not(controller.get("canMergePosts"), "false when selected posts are from different users");
|
||||||
|
|
||||||
|
selectedPostIds.replace(1, 1, [3]);
|
||||||
|
|
||||||
|
assert.not(controller.get("canMergePosts"), "false when selected posts can't be deleted");
|
||||||
|
|
||||||
|
selectedPostIds.replace(1, 1, [4]);
|
||||||
|
|
||||||
|
assert.ok(controller.get("canMergePosts"), "true when all selected posts are deletable and by the same user");
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("Select/deselect all", function(assert) {
|
||||||
|
const postStream = { stream: [1, 2, 3] };
|
||||||
|
const model = Topic.create({ postStream });
|
||||||
|
const controller = this.subject({ model });
|
||||||
|
|
||||||
|
assert.equal(controller.get("selectedPostsCount"), 0, "no posts selected by default");
|
||||||
|
|
||||||
|
controller.send("selectAll");
|
||||||
|
|
||||||
|
assert.equal(controller.get("selectedPostsCount"), postStream.stream.length, "calling 'selectAll' selects all posts");
|
||||||
|
|
||||||
|
controller.send("deselectAll");
|
||||||
|
|
||||||
|
assert.equal(controller.get("selectedPostsCount"), 0, "calling 'deselectAll' deselects all posts");
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("togglePostSelection", function(assert) {
|
||||||
|
const controller = this.subject();
|
||||||
|
const selectedPostIds = controller.get("selectedPostIds");
|
||||||
|
|
||||||
|
assert.equal(selectedPostIds[0], undefined, "no posts selected by default");
|
||||||
|
|
||||||
|
controller.send("togglePostSelection", { id: 1 });
|
||||||
|
|
||||||
|
assert.equal(selectedPostIds[0], 1, "adds the selected post id if not already selected");
|
||||||
|
|
||||||
|
controller.send("togglePostSelection", { id: 1 });
|
||||||
|
|
||||||
|
assert.equal(selectedPostIds[0], undefined, "removes the selected post id if already selected");
|
||||||
|
});
|
||||||
|
|
||||||
|
// QUnit.test("selectReplies", function(assert) {
|
||||||
|
// const controller = this.subject();
|
||||||
|
// const selectedPostIds = controller.get("selectedPostIds");
|
||||||
|
//
|
||||||
|
// assert.equal(selectedPostIds[0], undefined, "no posts selected by default");
|
||||||
|
//
|
||||||
|
// controller.send("selectReplies", { id: 42 });
|
||||||
|
//
|
||||||
|
// assert.equal(selectedPostIds[0], 42, "selected post #42");
|
||||||
|
// assert.equal(selectedPostIds[1], 45, "selected post #45");
|
||||||
|
// assert.equal(selectedPostIds[2], 100, "selected post #100");
|
||||||
|
// });
|
||||||
|
|
||||||
|
QUnit.test("selectBelow", function(assert) {
|
||||||
|
const postStream = { stream: [1, 2, 3, 4, 5] };
|
||||||
|
const model = Topic.create({ postStream });
|
||||||
|
const controller = this.subject({ model });
|
||||||
|
const selectedPostIds = controller.get("selectedPostIds");
|
||||||
|
|
||||||
|
assert.equal(selectedPostIds[0], undefined, "no posts selected by default");
|
||||||
|
|
||||||
|
controller.send("selectBelow", { id: 3 });
|
||||||
|
|
||||||
|
assert.equal(selectedPostIds[0], 3, "selected post #3");
|
||||||
|
assert.equal(selectedPostIds[1], 4, "also selected 1st post below post #3");
|
||||||
|
assert.equal(selectedPostIds[2], 5, "also selected 2nd post below post #3");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -306,6 +306,10 @@ export default function() {
|
||||||
return response(200, [ { id: 2222, post_number: 2222 } ]);
|
return response(200, [ { id: 2222, post_number: 2222 } ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.get("/posts/:post_id/reply-ids.json", () => {
|
||||||
|
return response(200, { direct_reply_ids: [45], all_reply_ids: [45, 100] });
|
||||||
|
});
|
||||||
|
|
||||||
this.post('/user_badges', () => response(200, fixturesByUrl['/user_badges']));
|
this.post('/user_badges', () => response(200, fixturesByUrl['/user_badges']));
|
||||||
this.delete('/user_badges/:badge_id', success);
|
this.delete('/user_badges/:badge_id', success);
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import { clearHTMLCache } from 'discourse/helpers/custom-html';
|
||||||
import { flushMap } from 'discourse/models/store';
|
import { flushMap } from 'discourse/models/store';
|
||||||
import { clearRewrites } from 'discourse/lib/url';
|
import { clearRewrites } from 'discourse/lib/url';
|
||||||
|
|
||||||
|
|
||||||
export function currentUser() {
|
export function currentUser() {
|
||||||
return Discourse.User.create(sessionFixtures['/session/current.json'].current_user);
|
return Discourse.User.create(sessionFixtures['/session/current.json'].current_user);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
QUnit.module("mixin:selected-posts-count");
|
|
||||||
|
|
||||||
import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
|
|
||||||
import Topic from 'discourse/models/topic';
|
|
||||||
|
|
||||||
var buildTestObj = function(params) {
|
|
||||||
return Ember.Object.extend(SelectedPostsCount).create(params || {});
|
|
||||||
};
|
|
||||||
|
|
||||||
QUnit.test("without selectedPosts", assert => {
|
|
||||||
var testObj = buildTestObj();
|
|
||||||
|
|
||||||
assert.equal(testObj.get('selectedPostsCount'), 0, "No posts are selected without a selectedPosts property");
|
|
||||||
|
|
||||||
testObj.set('selectedPosts', []);
|
|
||||||
assert.equal(testObj.get('selectedPostsCount'), 0, "No posts are selected when selectedPosts is an empty array");
|
|
||||||
});
|
|
||||||
|
|
||||||
QUnit.test("with some selectedPosts", assert => {
|
|
||||||
var testObj = buildTestObj({ selectedPosts: [Discourse.Post.create({id: 123})] });
|
|
||||||
assert.equal(testObj.get('selectedPostsCount'), 1, "It returns the amount of posts");
|
|
||||||
});
|
|
||||||
|
|
||||||
QUnit.test("when all posts are selected and there is a posts_count", assert => {
|
|
||||||
var testObj = buildTestObj({ allPostsSelected: true, posts_count: 1024 });
|
|
||||||
assert.equal(testObj.get('selectedPostsCount'), 1024, "It returns the posts_count");
|
|
||||||
});
|
|
||||||
|
|
||||||
QUnit.test("when all posts are selected and there is topic with a posts_count", assert => {
|
|
||||||
var testObj = buildTestObj({
|
|
||||||
allPostsSelected: true,
|
|
||||||
topic: Topic.create({ posts_count: 3456 })
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(testObj.get('selectedPostsCount'), 3456, "It returns the topic's posts_count");
|
|
||||||
});
|
|
Loading…
Reference in New Issue