FEATURE: move posts to new/existing PM (#6802)

FEATURE: move posts to new/existing PM (#6802)

diff --git a/app/assets/javascripts/discourse/components/choose-message.js.es6 b/app/assets/javascripts/discourse/components/choose-message.js.es6
new file mode 100644
index 0000000..b05dd54
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/choose-message.js.es6
@@ -0,0 +1,64 @@
+import debounce from "discourse/lib/debounce";
+import { searchForTerm } from "discourse/lib/search";
+import { observes } from "ember-addons/ember-computed-decorators";
+
+export default Ember.Component.extend({
+  loading: null,
+  noResults: null,
+  messages: null,
+
+  @observes("messageTitle")
+  messageTitleChanged() {
+    this.setProperties({
+      loading: true,
+      noResults: true,
+      selectedTopicId: null
+    });
+    this.search(this.get("messageTitle"));
+  },
+
+  @observes("messages")
+  messagesChanged() {
+    const messages = this.get("messages");
+    if (messages) {
+      this.set("noResults", messages.length === 0);
+    }
+    this.set("loading", false);
+  },
+
+  search: debounce(function(title) {
+    const currentTopicId = this.get("currentTopicId");
+
+    if (Em.isEmpty(title)) {
+      this.setProperties({ messages: null, loading: false });
+      return;
+    }
+
+    searchForTerm(title, {
+      typeFilter: "private_messages",
+      searchForId: true
+    }).then(results => {
+      if (results && results.posts && results.posts.length > 0) {
+        this.set(
+          "messages",
+          results.posts
+            .mapBy("topic")
+            .filter(t => t.get("id") !== currentTopicId)
+        );
+      } else {
+        this.setProperties({ messages: null, loading: false });
+      }
+    });
+  }, 300),
+
+  actions: {
+    chooseMessage(message) {
+      const messageId = Em.get(message, "id");
+      this.set("selectedTopicId", messageId);
+      Ember.run.next(() =>
+        $(`#choose-message-${messageId}`).prop("checked", "true")
+      );
+      return false;
+    }
+  }
+});
diff --git a/app/assets/javascripts/discourse/controllers/merge-topic.js.es6 b/app/assets/javascripts/discourse/controllers/merge-topic.js.es6
deleted file mode 100644
index b0d488e..0000000
--- a/app/assets/javascripts/discourse/controllers/merge-topic.js.es6
+++ /dev/null
@@ -1,59 +0,0 @@
-import ModalFunctionality from "discourse/mixins/modal-functionality";
-import { movePosts, mergeTopic } from "discourse/models/topic";
-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"),
-
-  saving: false,
-  selectedTopicId: null,
-
-  selectedPostsCount: Ember.computed.alias(
-    "topicController.selectedPostsCount"
-  ),
-
-  @computed("saving", "selectedTopicId")
-  buttonDisabled(saving, selectedTopicId) {
-    return saving || Ember.isEmpty(selectedTopicId);
-  },
-
-  @computed("saving")
-  buttonTitle(saving) {
-    return saving ? I18n.t("saving") : I18n.t("topic.merge_topic.title");
-  },
-
-  onShow() {
-    this.set("modal.modalClass", "split-modal");
-  },
-
-  actions: {
-    movePostsToExistingTopic() {
-      const topicId = this.get("model.id");
-
-      this.set("saving", true);
-
-      let promise = this.get("topicController.selectedAllPosts")
-        ? mergeTopic(topicId, this.get("selectedTopicId"))
-        : movePosts(topicId, {
-            destination_topic_id: this.get("selectedTopicId"),
-            post_ids: this.get("topicController.selectedPostIds")
-          });
-
-      promise
-        .then(result => {
-          this.send("closeModal");
-          this.get("topicController").send("toggleMultiSelect");
-          Ember.run.next(() => DiscourseURL.routeTo(result.url));
-        })
-        .catch(() => {
-          this.flash(I18n.t("topic.merge_topic.error"));
-        })
-        .finally(() => {
-          this.set("saving", false);
-        });
-
-      return false;
-    }
-  }
-});
diff --git a/app/assets/javascripts/discourse/controllers/move-to-topic.js.es6 b/app/assets/javascripts/discourse/controllers/move-to-topic.js.es6
new file mode 100644
index 0000000..6df047c
--- /dev/null
+++ b/app/assets/javascripts/discourse/controllers/move-to-topic.js.es6
@@ -0,0 +1,155 @@
+import ModalFunctionality from "discourse/mixins/modal-functionality";
+import { movePosts, mergeTopic } from "discourse/models/topic";
+import DiscourseURL from "discourse/lib/url";
+import { default as computed } from "ember-addons/ember-computed-decorators";
+import { extractError } from "discourse/lib/ajax-error";
+
+export default Ember.Controller.extend(ModalFunctionality, {
+  topicName: null,
+  saving: false,
+  categoryId: null,
+  tags: null,
+  canAddTags: Ember.computed.alias("site.can_create_tag"),
+  canTagMessages: Ember.computed.alias("site.can_tag_pms"),
+  selectedTopicId: null,
+  newTopic: Ember.computed.equal("selection", "new_topic"),
+  existingTopic: Ember.computed.equal("selection", "existing_topic"),
+  newMessage: Ember.computed.equal("selection", "new_message"),
+  existingMessage: Ember.computed.equal("selection", "existing_message"),
+  moveTypes: ["newTopic", "existingTopic", "newMessage", "existingMessage"],
+  participants: null,
+
+  topicController: Ember.inject.controller("topic"),
+  selectedPostsCount: Ember.computed.alias(
+    "topicController.selectedPostsCount"
+  ),
+  selectedAllPosts: Ember.computed.alias("topicController.selectedAllPosts"),
+  selectedPosts: Ember.computed.alias("topicController.selectedPosts"),
+
+  @computed("saving", "selectedTopicId", "topicName")
+  buttonDisabled(saving, selectedTopicId, topicName) {
+    return (
+      saving || (Ember.isEmpty(selectedTopicId) && Ember.isEmpty(topicName))
+    );
+  },
+
+  @computed(
+    "saving",
+    "newTopic",
+    "existingTopic",
+    "newMessage",
+    "existingMessage"
+  )
+  buttonTitle(saving, newTopic, existingTopic, newMessage, existingMessage) {
+    if (newTopic) {
+      return I18n.t("topic.split_topic.title");
+    } else if (existingTopic) {
+      return I18n.t("topic.merge_topic.title");
+    } else if (newMessage) {
+      return I18n.t("topic.move_to_new_message.title");
+    } else if (existingMessage) {
+      return I18n.t("topic.move_to_existing_message.title");
+    } else {
+      return I18n.t("saving");
+    }
+  },
+
+  onShow() {
+    this.setProperties({
+      "modal.modalClass": "move-to-modal",
+      saving: false,
+      selection: "new_topic",
+      categoryId: null,
+      topicName: "",
+      tags: null,
+      participants: null
+    });
+
+    const isPrivateMessage = this.get("model.isPrivateMessage");
+    const canSplitTopic = this.get("canSplitTopic");
+    if (isPrivateMessage) {
+      this.set("selection", canSplitTopic ? "new_message" : "existing_message");
+    } else if (!canSplitTopic) {
+      this.set("selection", "existing_topic");
+    }
+  },
+
+  @computed("selectedAllPosts", "selectedPosts", "selectedPosts.[]")
+  canSplitTopic(selectedAllPosts, selectedPosts) {
+    return (
+      !selectedAllPosts &&
+      selectedPosts.length > 0 &&
+      selectedPosts.sort((a, b) => a.post_number - b.post_number)[0]
+        .post_type === this.site.get("post_types.regular")
+    );
+  },
+
+  actions: {
+    performMove() {
+      this.get("moveTypes").forEach(type => {
+        if (this.get(type)) {
+          this.send("movePostsTo", type);
+        }
+      });
+    },
+
+    movePostsTo(type) {
+      this.set("saving", true);
+      const topicId = this.get("model.id");
+      let mergeOptions, moveOptions;
+
+      if (type === "existingTopic") {
+        mergeOptions = { destination_topic_id: this.get("selectedTopicId") };
+        moveOptions = Object.assign(
+          { post_ids: this.get("topicController.selectedPostIds") },
+          mergeOptions
+        );
+      } else if (type === "existingMessage") {
+        mergeOptions = {
+          destination_topic_id: this.get("selectedTopicId"),

[... diff too long, it was truncated ...]

GitHub
sha: 70fdc103

This commit has been mentioned on Discourse Meta. There might be relevant details there: