From 3a799ed922958f228ebba0695380c89dbf2ca672 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Wed, 12 Dec 2018 10:21:51 +0100 Subject: [PATCH] FEATURE: Check if draft exists before starting a new one (#6755) Co-Authored-By: Bianca Nenciu Co-Authored-By: zogstrip --- .../discourse/controllers/composer.js.es6 | 70 ++++++++++++++--- config/locales/client.en.yml | 4 + .../acceptance/composer-test.js.es6 | 77 ++++++++----------- .../composer-uncategorized-test.js.es6 | 56 ++++++++++++++ test/javascripts/acceptance/user-test.js.es6 | 10 ++- 5 files changed, 159 insertions(+), 58 deletions(-) create mode 100644 test/javascripts/acceptance/composer-uncategorized-test.js.es6 diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 042597354c..5e62a28d62 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 194a633a14..e9db885c89 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 898e815bf9..2362f26344 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("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( - exists(".d-editor-textarea-wrapper.disabled"), - "textarea is disabled" - ); - - const categoryChooser = selectKit(".category-chooser"); - - await categoryChooser.expand(); - await categoryChooser.selectRowByValue(2); - - assert.ok( - find(".d-editor-textarea-wrapper.disabled").length === 0, - "textarea is enabled" - ); - - await fillIn(".d-editor-input", "Now I can type stuff"); - await categoryChooser.expand(); - await categoryChooser.selectRowByValue("__none__"); - - assert.ok( - find(".d-editor-textarea-wrapper.disabled").length === 0, - "textarea is still enabled" - ); +QUnit.test("Checks for existing draft", async assert => { + toggleCheckDraftPopup(true); + + // 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("/t/internationalization-localization/280"); + + await click(".topic-post:eq(0) button.show-more-actions"); + await click(".topic-post:eq(0) button.edit"); + + assert.equal(find(".modal-body").text(), I18n.t("drafts.abandon.confirm")); + + await click(".modal-footer .btn.btn-default"); + + 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 0000000000..bbcc67a350 --- /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( + exists(".d-editor-textarea-wrapper.disabled"), + "textarea is disabled" + ); + + const categoryChooser = selectKit(".category-chooser"); + + await categoryChooser.expand(); + await categoryChooser.selectRowByValue(2); + + assert.ok( + find(".d-editor-textarea-wrapper.disabled").length === 0, + "textarea is enabled" + ); + + await fillIn(".d-editor-input", "Now I can type stuff"); + await categoryChooser.expand(); + await categoryChooser.selectRowByValue("__none__"); + + assert.ok( + find(".d-editor-textarea-wrapper.disabled").length === 0, + "textarea is still enabled" + ); +}); diff --git a/test/javascripts/acceptance/user-test.js.es6 b/test/javascripts/acceptance/user-test.js.es6 index b215b402e4..d4fcd33be6 100644 --- a/test/javascripts/acceptance/user-test.js.es6 +++ b/test/javascripts/acceptance/user-test.js.es6 @@ -41,10 +41,18 @@ QUnit.test("Viewing Summary", async assert => { }); QUnit.test("Viewing Drafts", async assert => { + // 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("/u/eviltrout/activity/drafts"); assert.ok(exists(".user-stream"), "has drafts stream"); assert.ok( - $(".user-stream .user-stream-item-draft-actions").length, + exists(".user-stream .user-stream-item-draft-actions"), "has draft action buttons" );