/** This controller supports all actions related to a topic @class TopicController @extends Discourse.ObjectController @namespace Discourse @module Discourse **/ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.SelectedPostsCount, { multiSelect: false, needs: ['header', 'modal', 'composer', 'quoteButton'], allPostsSelected: false, editingTopic: false, selectedPosts: null, selectedReplies: null, queryParams: ['filter', 'username_filters'], filter: function(key, value) { if (arguments.length > 1) { this.set('postStream.summary', value === "summary"); } return this.get('postStream.summary') ? "summary" : null; }.property('postStream.summary'), username_filters: Discourse.computed.queryAlias('postStream.streamFilters.username_filters'), init: function() { this._super(); this.set('selectedPosts', new Em.Set()); this.set('selectedReplies', new Em.Set()); }, actions: { jumpTop: function() { Discourse.URL.routeTo(this.get('firstPostUrl')); }, jumpBottom: function() { Discourse.URL.routeTo(this.get('lastPostUrl')); }, selectAll: function() { var posts = this.get('postStream.posts'), selectedPosts = this.get('selectedPosts'); if (posts) { selectedPosts.addObjects(posts); } this.set('allPostsSelected', true); }, deselectAll: function() { this.get('selectedPosts').clear(); this.get('selectedReplies').clear(); this.set('allPostsSelected', false); }, /** Toggle a participant for filtering @method toggleParticipant **/ toggleParticipant: function(user) { this.get('postStream').toggleParticipant(Em.get(user, 'username')); }, editTopic: function() { if (!this.get('details.can_edit')) return false; this.setProperties({ editingTopic: true, newTitle: this.get('title'), newCategoryId: this.get('category_id') }); return false; }, // close editing mode cancelEditingTopic: function() { this.set('editingTopic', false); }, toggleMultiSelect: function() { this.toggleProperty('multiSelect'); }, finishedEditingTopic: function() { var topicController = this; if (this.get('editingTopic')) { var topic = this.get('model'); // Topic title hasn't been sanitized yet, so the template shouldn't trust it. this.set('topicSaving', true); // manually update the titles & category topic.setProperties({ title: this.get('newTitle'), category_id: parseInt(this.get('newCategoryId'), 10), fancy_title: this.get('newTitle') }); // save the modifications topic.save().then(function(result){ // update the title if it has been changed (cleaned up) server-side var title = result.basic_topic.title; var fancy_title = result.basic_topic.fancy_title; topic.setProperties({ title: title, fancy_title: fancy_title }); topicController.set('topicSaving', false); }, function(error) { topicController.set('editingTopic', true); topicController.set('topicSaving', false); if (error && error.responseText) { bootbox.alert($.parseJSON(error.responseText).errors[0]); } else { bootbox.alert(I18n.t('generic_error')); } }); // close editing mode topicController.set('editingTopic', false); } }, toggledSelectedPost: function(post) { this.performTogglePost(post); }, toggledSelectedPostReplies: function(post) { var selectedReplies = this.get('selectedReplies'); if (this.performTogglePost(post)) { selectedReplies.addObject(post); } else { selectedReplies.removeObject(post); } }, deleteSelected: function() { var self = this; bootbox.confirm(I18n.t("post.delete.confirm", { count: this.get('selectedPostsCount')}), function(result) { if (result) { // If all posts are selected, it's the same thing as deleting the topic if (self.get('allPostsSelected')) { return self.deleteTopic(); } var selectedPosts = self.get('selectedPosts'), selectedReplies = self.get('selectedReplies'), postStream = self.get('postStream'), toRemove = new Ember.Set(); Discourse.Post.deleteMany(selectedPosts, selectedReplies); postStream.get('posts').forEach(function (p) { if (self.postSelected(p)) { toRemove.addObject(p); } }); postStream.removePosts(toRemove); self.send('toggleMultiSelect'); } }); }, toggleVisibility: function() { this.get('content').toggleStatus('visible'); }, toggleClosed: function() { this.get('content').toggleStatus('closed'); }, togglePinned: function() { // Note that this is different than clearPin this.get('content').setStatus('pinned', this.get('pinned_at') ? false : true); }, togglePinnedGlobally: function() { // Note that this is different than clearPin this.get('content').setStatus('pinned_globally', this.get('pinned_at') ? false : true); }, toggleArchived: function() { this.get('content').toggleStatus('archived'); }, convertToRegular: function() { this.get('content').convertArchetype('regular'); }, // Toggle the star on the topic toggleStar: function() { this.get('content').toggleStar(); }, /** Clears the pin from a topic for the currently logged in user @method clearPin **/ clearPin: function() { this.get('content').clearPin(); }, resetRead: function() { Discourse.ScreenTrack.current().reset(); this.unsubscribe(); var topicController = this; this.get('model').resetRead().then(function() { topicController.set('message', I18n.t("topic.read_position_reset")); topicController.set('postStream.loaded', false); }); }, replyAsNewTopic: function(post) { var composerController = this.get('controllers.composer'), promise = composerController.open({ action: Discourse.Composer.CREATE_TOPIC, draftKey: Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY }), postUrl = "" + location.protocol + "//" + location.host + (post.get('url')), postLink = "[" + (this.get('title')) + "](" + postUrl + ")"; promise.then(function() { Discourse.Post.loadQuote(post.get('id')).then(function(q) { composerController.appendText(I18n.t("post.continue_discussion", { postLink: postLink }) + "\n\n" + q); }); }); }, expandFirstPost: function(post) { var self = this; this.set('loadingExpanded', true); post.expand().then(function() { self.set('firstPostExpanded', true); }).catch(function(error) { bootbox.alert($.parseJSON(error.responseText).errors); }).finally(function() { self.set('loadingExpanded', false); }); } }, showExpandButton: function() { var post = this.get('post'); return post.get('post_number') === 1 && post.get('topic.expandable_first_post'); }.property(), slackRatio: function() { return Discourse.Capabilities.currentProp('slackRatio'); }.property(), jumpTopDisabled: function() { return (this.get('progressPosition') < 2); }.property('progressPosition'), jumpBottomDisabled: function() { return this.get('progressPosition') >= this.get('postStream.filteredPostsCount') || this.get('progressPosition') >= this.get('highest_post_number'); }.property('postStream.filteredPostsCount', 'highest_post_number', 'progressPosition'), canMergeTopic: function() { if (!this.get('details.can_move_posts')) return false; return (this.get('selectedPostsCount') > 0); }.property('selectedPostsCount'), canSplitTopic: function() { if (!this.get('details.can_move_posts')) return false; if (this.get('allPostsSelected')) return false; return (this.get('selectedPostsCount') > 0); }.property('selectedPostsCount'), canChangeOwner: function() { if (!Discourse.User.current() || !Discourse.User.current().admin) return false; return !!this.get('selectedPostsUsername'); }.property('selectedPostsUsername'), categories: function() { return Discourse.Category.list(); }.property(), canSelectAll: Em.computed.not('allPostsSelected'), canDeselectAll: function () { if (this.get('selectedPostsCount') > 0) return true; if (this.get('allPostsSelected')) return true; }.property('selectedPostsCount', 'allPostsSelected'), canDeleteSelected: function() { var selectedPosts = this.get('selectedPosts'); if (this.get('allPostsSelected')) return true; if (this.get('selectedPostsCount') === 0) return false; var canDelete = true; selectedPosts.forEach(function(p) { if (!p.get('can_delete')) { canDelete = false; return false; } }); return canDelete; }.property('selectedPostsCount'), hasError: Ember.computed.or('errorBodyHtml', 'message'), streamPercentage: function() { if (!this.get('postStream.loaded')) { return 0; } if (this.get('postStream.highest_post_number') === 0) { return 0; } var perc = this.get('progressPosition') / this.get('postStream.filteredPostsCount'); return (perc > 1.0) ? 1.0 : perc; }.property('postStream.loaded', 'progressPosition', 'postStream.filteredPostsCount'), multiSelectChanged: function() { // Deselect all posts when multi select is turned off if (!this.get('multiSelect')) { this.send('deselectAll'); } }.observes('multiSelect'), hideProgress: function() { if (!this.get('postStream.loaded')) return true; if (!this.get('currentPost')) return true; if (this.get('postStream.filteredPostsCount') < 2) return true; return false; }.property('postStream.loaded', 'currentPost', 'postStream.filteredPostsCount'), hugeNumberOfPosts: function() { return (this.get('postStream.filteredPostsCount') >= Discourse.SiteSettings.short_progress_text_threshold); }.property('highest_post_number'), jumpToBottomTitle: function() { if (this.get('hugeNumberOfPosts')) { return I18n.t('topic.progress.jump_bottom_with_number', {post_number: this.get('highest_post_number')}); } else { return I18n.t('topic.progress.jump_bottom'); } }.property('hugeNumberOfPosts', 'highest_post_number'), deselectPost: function(post) { this.get('selectedPosts').removeObject(post); var selectedReplies = this.get('selectedReplies'); selectedReplies.removeObject(post); var selectedReply = selectedReplies.findProperty('post_number', post.get('reply_to_post_number')); if (selectedReply) { selectedReplies.removeObject(selectedReply); } this.set('allPostsSelected', false); }, postSelected: function(post) { if (this.get('allPostsSelected')) { return true; } if (this.get('selectedPosts').contains(post)) { return true; } if (this.get('selectedReplies').findProperty('post_number', post.get('reply_to_post_number'))) { return true; } return false; }, showStarButton: function() { return Discourse.User.current() && !this.get('isPrivateMessage'); }.property('isPrivateMessage'), loadingHTML: function() { return "