FEATURE: Check if draft exists before starting a new one (#6755)

FEATURE: Check if draft exists before starting a new one (#6755)

Co-Authored-By: Bianca Nenciu nbianca@users.noreply.github.com
Co-Authored-By: zogstrip regis@hanol.fr

Co-Authored-By: Bianca Nenciu <nbianca@users.noreply.github.com>
Co-Authored-By: zogstrip <regis@hanol.fr>

diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6
index 0425973..5e62a28 100644
--- a/app/assets/javascripts/discourse/controllers/composer.js.es6
+++ b/app/assets/javascripts/discourse/controllers/composer.js.es6
@@ -62,6 +62,12 @@ function loadDraft(store, opts) {
 
 const _popupMenuOptionsCallbacks = [];
 
+let _checkDraftPopup = !Ember.testing;
+
+export function toggleCheckDraftPopup(enabled) {
+  _checkDraftPopup = enabled;
+}
+
 export function clearPopupMenuOptionsCallback() {
   _popupMenuOptionsCallbacks.length = 0;
 }
@@ -770,23 +776,30 @@ export default Ember.Controller.extend({
           .then(resolve, reject);
       }
 
-      // we need a draft sequence for the composer to work
-      if (opts.draftSequence === undefined) {
-        return Draft.get(opts.draftKey)
-          .then(function(data) {
-            opts.draftSequence = data.draft_sequence;
-            opts.draft = data.draft;
-            self._setModel(composerModel, opts);
-          })
-          .then(resolve, reject);
-      }
-
       if (composerModel) {
         if (composerModel.get("action") !== opts.action) {
           composerModel.setProperties({ unlistTopic: false, whisper: false });
         }
       }
 
+      // check if there is another draft saved on server
+      // or get a draft sequence number
+      if (!opts.draft || opts.draftSequence === undefined) {
+        return Draft.get(opts.draftKey)
+          .then(data => self.confirmDraftAbandon(data))
+          .then(data => {
+            opts.draft = opts.draft || data.draft;
+
+            // we need a draft sequence for the composer to work
+            if (opts.draft_sequence === undefined) {
+              opts.draftSequence = data.draft_sequence;
+            }
+
+            self._setModel(composerModel, opts);
+          })
+          .then(resolve, reject);
+      }
+
       self._setModel(composerModel, opts);
       resolve();
     });
@@ -865,6 +878,41 @@ export default Ember.Controller.extend({
     }
   },
 
+  confirmDraftAbandon(data) {
+    if (!data.draft) {
+      return data;
+    }
+
+    // do not show abandon dialog if old draft is clean
+    const draft = JSON.parse(data.draft);
+    if (draft.reply === draft.originalText) {
+      data.draft = null;
+      return data;
+    }
+
+    if (_checkDraftPopup) {
+      return new Ember.RSVP.Promise(resolve => {
+        bootbox.dialog(I18n.t("drafts.abandon.confirm"), [
+          {
+            label: I18n.t("drafts.abandon.no_value"),
+            callback: () => resolve(data)
+          },
+          {
+            label: I18n.t("drafts.abandon.yes_value"),
+            class: "btn-danger",
+            callback: () => {
+              data.draft = null;
+              resolve(data);
+            }
+          }
+        ]);
+      });
+    } else {
+      data.draft = null;
+      return data;
+    }
+  },
+
   cancelComposer() {
     return new Ember.RSVP.Promise(resolve => {
       if (this.get("model.hasMetaData") || this.get("model.replyDirty")) {
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 194a633..e9db885 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -296,6 +296,10 @@ en:
       new_topic: "New topic draft"
       new_private_message: "New private message draft"
       topic_reply: "Draft reply"
+      abandon:
+        confirm: "You already opened another draft in this topic. Are you sure you want to abandon it?"
+        yes_value: "Yes, abandon"
+        no_value: "No, keep"
 
     topic_count_latest:
       one: "See {{count}} new or updated topic"
diff --git a/test/javascripts/acceptance/composer-test.js.es6 b/test/javascripts/acceptance/composer-test.js.es6
index 898e815..2362f26 100644
--- a/test/javascripts/acceptance/composer-test.js.es6
+++ b/test/javascripts/acceptance/composer-test.js.es6
@@ -1,7 +1,16 @@
-import { acceptance, replaceCurrentUser } from "helpers/qunit-helpers";
+import { acceptance } from "helpers/qunit-helpers";
+import { toggleCheckDraftPopup } from "discourse/controllers/composer";
 
 acceptance("Composer", {
   loggedIn: true,
+  pretend(server, helper) {
+    server.get("/draft.json", () => {
+      return helper.response({
+        draft: null,
+        draft_sequence: 42
+      });
+    });
+  },
   settings: {
     enable_whispers: true
   }
@@ -527,49 +536,25 @@ QUnit.test(
   }
 );
 
-acceptance("Composer and uncategorized is not allowed", {
-  loggedIn: true,
-  settings: {
-    enable_whispers: true,
-    allow_uncategorized_topics: false
-  }
-});
+QUnit.test("Checks for existing draft", async assert => {
+  toggleCheckDraftPopup(true);
 
-QUnit.test("Disable body until category is selected", async assert => {
-  replaceCurrentUser({ admin: false, staff: false, trust_level: 1 });
+  // prettier-ignore
+  server.get("/draft.json", () => { // eslint-disable-line no-undef
+    return [ 200, { "Content-Type": "application/json" }, {
+      draft: "{\"reply\":\"This is a draft of the first post\",\"action\":\"reply\",\"categoryId\":1,\"archetypeId\":\"regular\",\"metaData\":null,\"composerTime\":2863,\"typingTime\":200}",
+      draft_sequence: 42
+    } ];
+  });
 
-  await visit("/");
-  await click("#create-topic");
-  assert.ok(exists(".d-editor-input"), "the composer input is visible");
-  assert.ok(
-    exists(".title-input .popup-tip.bad.hide"),
-    "title errors are hidden by default"
-  );
-  assert.ok(
-    exists(".d-editor-textarea-wrapper .popup-tip.bad.hide"),
-    "body errors are hidden by default"
-  );
-  assert.ok(
-    exists(".d-editor-textarea-wrapper.disabled"),
-    "textarea is disabled"
-  );
-
-  const categoryChooser = selectKit(".category-chooser");
+  await visit("/t/internationalization-localization/280");
 
-  await categoryChooser.expand();
-  await categoryChooser.selectRowByValue(2);
+  await click(".topic-post:eq(0) button.show-more-actions");
+  await click(".topic-post:eq(0) button.edit");
 
-  assert.ok(
-    find(".d-editor-textarea-wrapper.disabled").length === 0,
-    "textarea is enabled"
-  );
+  assert.equal(find(".modal-body").text(), I18n.t("drafts.abandon.confirm"));
 
-  await fillIn(".d-editor-input", "Now I can type stuff");
-  await categoryChooser.expand();
-  await categoryChooser.selectRowByValue("__none__");
+  await click(".modal-footer .btn.btn-default");
 
-  assert.ok(
-    find(".d-editor-textarea-wrapper.disabled").length === 0,
-    "textarea is still enabled"
-  );
+  toggleCheckDraftPopup(false);
 });
diff --git a/test/javascripts/acceptance/composer-uncategorized-test.js.es6 b/test/javascripts/acceptance/composer-uncategorized-test.js.es6
new file mode 100644
index 0000000..bbcc67a
--- /dev/null
+++ b/test/javascripts/acceptance/composer-uncategorized-test.js.es6
@@ -0,0 +1,56 @@
+import { acceptance, replaceCurrentUser } from "helpers/qunit-helpers";
+
+acceptance("Composer and uncategorized is not allowed", {
+  loggedIn: true,
+  pretend(server, helper) {
+    server.get("/draft.json", () => {
+      return helper.response({
+        draft: null,
+        draft_sequence: 42
+      });
+    });
+  },
+  settings: {
+    enable_whispers: true,
+    allow_uncategorized_topics: false
+  }
+});
+
+QUnit.test("Disable body until category is selected", async assert => {
+  replaceCurrentUser({ admin: false, staff: false, trust_level: 1 });
+
+  await visit("/");
+  await click("#create-topic");
+  assert.ok(exists(".d-editor-input"), "the composer input is visible");
+  assert.ok(
+    exists(".title-input .popup-tip.bad.hide"),
+    "title errors are hidden by default"
+  );
+  assert.ok(
+    exists(".d-editor-textarea-wrapper .popup-tip.bad.hide"),
+    "body errors are hidden by default"
+  );
+  assert.ok(

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

GitHub

1 Like