From 76aa0795b35f3153a006084334c59090d61ec3b6 Mon Sep 17 00:00:00 2001
From: Robin Ward <robin.ward@gmail.com>
Date: Fri, 31 Jul 2015 16:30:18 -0400
Subject: [PATCH] Use small actions for moving posts

---
 .../discourse/components/small-action.js.es6  |  3 +-
 .../discourse/controllers/merge-topic.js.es6  | 37 ++++++++------
 .../discourse/controllers/split-topic.js.es6  | 49 +++++++++----------
 .../discourse/lib/ajax-error.js.es6           |  4 +-
 .../javascripts/discourse/models/topic.js.es6 | 43 ++++++++--------
 .../discourse/templates/modal/merge-topic.hbs | 10 ++--
 .../{split_topic.hbs => split-topic.hbs}      | 10 ++--
 .../discourse/views/split-topic.js.es6        |  2 +-
 app/assets/javascripts/main_include.js        |  2 +
 app/models/post_mover.rb                      |  8 ++-
 config/locales/client.en.yml                  |  1 +
 config/locales/server.en.yml                  |  8 +--
 12 files changed, 88 insertions(+), 89 deletions(-)
 rename app/assets/javascripts/discourse/templates/modal/{split_topic.hbs => split-topic.hbs} (59%)

diff --git a/app/assets/javascripts/discourse/components/small-action.js.es6 b/app/assets/javascripts/discourse/components/small-action.js.es6
index 977b408c807..4ffa7ac63f2 100644
--- a/app/assets/javascripts/discourse/components/small-action.js.es6
+++ b/app/assets/javascripts/discourse/components/small-action.js.es6
@@ -10,7 +10,8 @@ const icons = {
   'pinned_globally.enabled': 'thumb-tack',
   'pinned_globally.disabled': 'thumb-tack unpinned',
   'visible.enabled': 'eye',
-  'visible.disabled': 'eye-slash'
+  'visible.disabled': 'eye-slash',
+  'split_topic': 'sign-out'
 };
 
 export function actionDescription(actionCode, createdAt) {
diff --git a/app/assets/javascripts/discourse/controllers/merge-topic.js.es6 b/app/assets/javascripts/discourse/controllers/merge-topic.js.es6
index c923aaf2dab..44f374ddbf8 100644
--- a/app/assets/javascripts/discourse/controllers/merge-topic.js.es6
+++ b/app/assets/javascripts/discourse/controllers/merge-topic.js.es6
@@ -1,12 +1,15 @@
 import Presence from 'discourse/mixins/presence';
 import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
 import ModalFunctionality from 'discourse/mixins/modal-functionality';
-import ObjectController from 'discourse/controllers/object';
+import { movePosts, mergeTopic } from 'discourse/models/topic';
 
 // Modal related to merging of topics
-export default ObjectController.extend(SelectedPostsCount, ModalFunctionality, Presence, {
+export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, Presence, {
   needs: ['topic'],
 
+  saving: false,
+  selectedTopicId: null,
+
   topicController: Em.computed.alias('controllers.topic'),
   selectedPosts: Em.computed.alias('topicController.selectedPosts'),
   selectedReplies: Em.computed.alias('topicController.selectedReplies'),
@@ -22,38 +25,40 @@ export default ObjectController.extend(SelectedPostsCount, ModalFunctionality, P
     return I18n.t('topic.merge_topic.title');
   }.property('saving'),
 
-  onShow: function() {
+  onShow() {
     this.set('controllers.modal.modalClass', 'split-modal');
   },
 
   actions: {
-    movePostsToExistingTopic: function() {
+    movePostsToExistingTopic() {
+      const topicId = this.get('model.id');
+
       this.set('saving', true);
 
-      var promise = null;
+      let promise = null;
       if (this.get('allPostsSelected')) {
-        promise = Discourse.Topic.mergeTopic(this.get('id'), this.get('selectedTopicId'));
+        promise = mergeTopic(topicId, this.get('selectedTopicId'));
       } else {
-        var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }),
-            replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); });
+        const postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); });
+        const replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); });
 
-        promise = Discourse.Topic.movePosts(this.get('id'), {
+        promise = movePosts(topicId, {
           destination_topic_id: this.get('selectedTopicId'),
           post_ids: postIds,
           reply_post_ids: replyPostIds
         });
       }
 
-      var mergeTopicController = this;
+      const self = this;
       promise.then(function(result) {
         // Posts moved
-        mergeTopicController.send('closeModal');
-        mergeTopicController.get('topicController').send('toggleMultiSelect');
+        self.send('closeModal');
+        self.get('topicController').send('toggleMultiSelect');
         Em.run.next(function() { Discourse.URL.routeTo(result.url); });
-      }, function() {
-        // Error moving posts
-        mergeTopicController.flash(I18n.t('topic.merge_topic.error'));
-        mergeTopicController.set('saving', false);
+      }).catch(function() {
+        self.flash(I18n.t('topic.merge_topic.error'));
+      }).finally(function() {
+        self.set('saving', false);
       });
       return false;
     }
diff --git a/app/assets/javascripts/discourse/controllers/split-topic.js.es6 b/app/assets/javascripts/discourse/controllers/split-topic.js.es6
index 98aed2dccea..0c75ae35037 100644
--- a/app/assets/javascripts/discourse/controllers/split-topic.js.es6
+++ b/app/assets/javascripts/discourse/controllers/split-topic.js.es6
@@ -1,15 +1,20 @@
 import Presence from 'discourse/mixins/presence';
 import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
 import ModalFunctionality from 'discourse/mixins/modal-functionality';
-import ObjectController from 'discourse/controllers/object';
+import { extractError } from 'discourse/lib/ajax-error';
+import { movePosts } from 'discourse/models/topic';
 
 // Modal related to auto closing of topics
-export default ObjectController.extend(SelectedPostsCount, ModalFunctionality, Presence, {
+export default Ember.Controller.extend(SelectedPostsCount, ModalFunctionality, Presence, {
   needs: ['topic'],
+  topicName: null,
+  saving: false,
+  categoryId: null,
 
   topicController: Em.computed.alias('controllers.topic'),
   selectedPosts: Em.computed.alias('topicController.selectedPosts'),
   selectedReplies: Em.computed.alias('topicController.selectedReplies'),
+  allPostsSelected: Em.computed.alias('topicController.allPostsSelected'),
 
   buttonDisabled: function() {
     if (this.get('saving')) return true;
@@ -21,7 +26,7 @@ export default ObjectController.extend(SelectedPostsCount, ModalFunctionality, P
     return I18n.t('topic.split_topic.action');
   }.property('saving'),
 
-  onShow: function() {
+  onShow() {
     this.setProperties({
       'controllers.modal.modalClass': 'split-modal',
       saving: false,
@@ -31,39 +36,29 @@ export default ObjectController.extend(SelectedPostsCount, ModalFunctionality, P
   },
 
   actions: {
-    movePostsToNewTopic: function() {
+    movePostsToNewTopic() {
       this.set('saving', true);
 
-      var postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }),
-          replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); }),
-          self = this,
-          categoryId = this.get('categoryId'),
-          saveOpts = {
-            title: this.get('topicName'),
-            post_ids: postIds,
-            reply_post_ids: replyPostIds
-          };
+      const postIds = this.get('selectedPosts').map(function(p) { return p.get('id'); }),
+            replyPostIds = this.get('selectedReplies').map(function(p) { return p.get('id'); }),
+            self = this,
+            categoryId = this.get('categoryId'),
+            saveOpts = {
+              title: this.get('topicName'),
+              post_ids: postIds,
+              reply_post_ids: replyPostIds
+            };
 
       if (!Ember.isNone(categoryId)) { saveOpts.category_id = categoryId; }
 
-      Discourse.Topic.movePosts(this.get('id'), saveOpts).then(function(result) {
+      movePosts(this.get('model.id'), saveOpts).then(function(result) {
         // Posts moved
         self.send('closeModal');
         self.get('topicController').send('toggleMultiSelect');
-        Em.run.next(function() { Discourse.URL.routeTo(result.url); });
+        Ember.run.next(function() { Discourse.URL.routeTo(result.url); });
       }).catch(function(xhr) {
-
-        var error = I18n.t('topic.split_topic.error');
-
-        if (xhr) {
-          var json = xhr.responseJSON;
-          if (json && json.errors) {
-            error = json.errors[0];
-          }
-        }
-
-        // Error moving posts
-        self.flash(error);
+        self.flash(extractError(xhr, I18n.t('topic.split_topic.error')));
+      }).finally(function() {
         self.set('saving', false);
       });
       return false;
diff --git a/app/assets/javascripts/discourse/lib/ajax-error.js.es6 b/app/assets/javascripts/discourse/lib/ajax-error.js.es6
index 04d6d0dbc7e..bb2a915727f 100644
--- a/app/assets/javascripts/discourse/lib/ajax-error.js.es6
+++ b/app/assets/javascripts/discourse/lib/ajax-error.js.es6
@@ -1,4 +1,4 @@
-function extractError(error) {
+export function extractError(error, defaultMessage) {
   if (error instanceof Error) {
     Ember.Logger.error(error.stack);
   }
@@ -42,7 +42,7 @@ function extractError(error) {
     }
   }
 
-  return parsedError || I18n.t('generic_error');
+  return parsedError || defaultMessage || I18n.t('generic_error');
 }
 
 export function throwAjaxError(undoCallback) {
diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6
index 0f06dc48471..24550874112 100644
--- a/app/assets/javascripts/discourse/models/topic.js.es6
+++ b/app/assets/javascripts/discourse/models/topic.js.es6
@@ -1,3 +1,4 @@
+import { flushMap } from 'discourse/models/store';
 import RestModel from 'discourse/models/rest';
 
 const Topic = RestModel.extend({
@@ -462,28 +463,6 @@ Topic.reopenClass({
     return Discourse.ajax(url + ".json", {data: data});
   },
 
-  mergeTopic(topicId, destinationTopicId) {
-    const promise = Discourse.ajax("/t/" + topicId + "/merge-topic", {
-      type: 'POST',
-      data: {destination_topic_id: destinationTopicId}
-    }).then(function (result) {
-      if (result.success) return result;
-      promise.reject(new Error("error merging topic"));
-    });
-    return promise;
-  },
-
-  movePosts(topicId, opts) {
-    const promise = Discourse.ajax("/t/" + topicId + "/move-posts", {
-      type: 'POST',
-      data: opts
-    }).then(function (result) {
-      if (result.success) return result;
-      promise.reject(new Error("error moving posts topic"));
-    });
-    return promise;
-  },
-
   changeOwners(topicId, opts) {
     const promise = Discourse.ajax("/t/" + topicId + "/change-owner", {
       type: 'POST',
@@ -523,4 +502,24 @@ Topic.reopenClass({
   }
 });
 
+function moveResult(result) {
+  if (result.success) {
+    // We should be hesitant to flush the map but moving ids is one rare case
+    flushMap();
+    return result;
+  }
+  throw "error moving posts topic";
+}
+
+export function movePosts(topicId, data) {
+  return Discourse.ajax("/t/" + topicId + "/move-posts", { type: 'POST', data }).then(moveResult);
+}
+
+export function mergeTopic(topicId, destinationTopicId) {
+  return Discourse.ajax("/t/" + topicId + "/merge-topic", {
+    type: 'POST',
+    data: {destination_topic_id: destinationTopicId}
+  }).then(moveResult);
+}
+
 export default Topic;
diff --git a/app/assets/javascripts/discourse/templates/modal/merge-topic.hbs b/app/assets/javascripts/discourse/templates/modal/merge-topic.hbs
index a05ac14f22d..e4bf364fc2e 100644
--- a/app/assets/javascripts/discourse/templates/modal/merge-topic.hbs
+++ b/app/assets/javascripts/discourse/templates/modal/merge-topic.hbs
@@ -1,10 +1,4 @@
 <div id='move-selected' class="modal-body">
-  {{#if error}}
-    <div class="alert alert-error">
-      <button class="close" data-dismiss="alert">×</button>
-    </div>
-  {{/if}}
-
   <p>{{{i18n 'topic.merge_topic.instructions' count=selectedPostsCount}}}</p>
 
   <form>
@@ -13,5 +7,7 @@
 </div>
 
 <div class="modal-footer">
-  <button class='btn btn-primary' {{bind-attr disabled="buttonDisabled"}} {{action "movePostsToExistingTopic"}}><i class="fa fa-sign-out"></i>{{buttonTitle}}</button>
+  {{#d-button class="btn-primary" disabled=buttonDisabled action="movePostsToExistingTopic"}}
+    {{fa-icon 'sign-out'}} {{buttonTitle}}
+  {{/d-button}}
 </div>
diff --git a/app/assets/javascripts/discourse/templates/modal/split_topic.hbs b/app/assets/javascripts/discourse/templates/modal/split-topic.hbs
similarity index 59%
rename from app/assets/javascripts/discourse/templates/modal/split_topic.hbs
rename to app/assets/javascripts/discourse/templates/modal/split-topic.hbs
index ff72aab89c5..fe80f88174a 100644
--- a/app/assets/javascripts/discourse/templates/modal/split_topic.hbs
+++ b/app/assets/javascripts/discourse/templates/modal/split-topic.hbs
@@ -1,10 +1,4 @@
 <div id='move-selected' class="modal-body">
-  {{#if error}}
-    <div class="alert alert-error">
-      <button class="close" data-dismiss="alert">×</button>
-    </div>
-  {{/if}}
-
   {{{i18n 'topic.split_topic.instructions' count=selectedPostsCount}}}
 
   <form>
@@ -18,5 +12,7 @@
 </div>
 
 <div class="modal-footer">
-  <button class='btn btn-primary' {{bind-attr disabled="buttonDisabled"}} {{action "movePostsToNewTopic"}}><i class='fa fa-sign-out'></i>{{buttonTitle}}</button>
+  {{#d-button class="btn-primary" disabled=buttonDisabled action="movePostsToNewTopic"}}
+    {{fa-icon 'sign-out'}} {{buttonTitle}}
+  {{/d-button}}
 </div>
diff --git a/app/assets/javascripts/discourse/views/split-topic.js.es6 b/app/assets/javascripts/discourse/views/split-topic.js.es6
index 5ee0d765548..9291495adc8 100644
--- a/app/assets/javascripts/discourse/views/split-topic.js.es6
+++ b/app/assets/javascripts/discourse/views/split-topic.js.es6
@@ -2,6 +2,6 @@ import SelectedPostsCount from 'discourse/mixins/selected-posts-count';
 import ModalBodyView from "discourse/views/modal-body";
 
 export default ModalBodyView.extend(SelectedPostsCount, {
-  templateName: 'modal/split_topic',
+  templateName: 'modal/split-topic',
   title: I18n.t('topic.split_topic.title')
 });
diff --git a/app/assets/javascripts/main_include.js b/app/assets/javascripts/main_include.js
index 668234d4faa..8338d7921f7 100644
--- a/app/assets/javascripts/main_include.js
+++ b/app/assets/javascripts/main_include.js
@@ -28,6 +28,8 @@
 //= require_tree ./discourse/adapters
 //= require ./discourse/models/rest
 //= require ./discourse/models/model
+//= require ./discourse/models/result-set
+//= require ./discourse/models/store
 //= require ./discourse/models/post-action-type
 //= require ./discourse/models/action-summary
 //= require ./discourse/models/post
diff --git a/app/models/post_mover.rb b/app/models/post_mover.rb
index 77111f6151a..272eb1c3f4f 100644
--- a/app/models/post_mover.rb
+++ b/app/models/post_mover.rb
@@ -123,11 +123,15 @@ class PostMover
   end
 
   def create_moderator_post_in_original_topic
+    move_type_str = PostMover.move_types[@move_type].to_s
+
     original_topic.add_moderator_post(
       user,
-      I18n.t("move_posts.#{PostMover.move_types[@move_type]}_moderator_post",
+      I18n.t("move_posts.#{move_type_str}_moderator_post",
              count: post_ids.count,
-             topic_link: "[#{destination_topic.title}](#{destination_topic.url})"),
+             topic_link: "[#{destination_topic.title}](#{destination_topic.relative_url})"),
+      post_type: Post.types[:small_action],
+      action_code: "split_topic",
       post_number: @first_post_number_moved
     )
   end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index f6034fac9a4..ce2a7574a27 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -120,6 +120,7 @@ en:
       email: 'send this link in an email'
 
     action_codes:
+      split_topic: "split this topic"
       autoclosed:
         enabled: 'closed this topic %{when}'
         disabled: 'opened this topic %{when}'
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 685aa844739..a390ee8a556 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1242,11 +1242,11 @@ en:
 
   move_posts:
     new_topic_moderator_post:
-      one: "I moved a post to a new topic: %{topic_link}"
-      other: "I moved %{count} posts to a new topic: %{topic_link}"
+      one: "A post was split to a new topic: %{topic_link}"
+      other: "%{count} posts were split to a new topic: %{topic_link}"
     existing_topic_moderator_post:
-      one: "I moved a post to an existing topic: %{topic_link}"
-      other: "I moved %{count} posts to an existing topic: %{topic_link}"
+      one: "A post was merged into an existing topic: %{topic_link}"
+      other: "%{count} posts were merged into an existing topic: %{topic_link}"
 
   change_owner:
     post_revision_text: "Ownership transferred from %{old_user} to %{new_user}"