mirror of
https://github.com/discourse/discourse.git
synced 2025-06-06 13:06:56 +08:00
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>
This commit is contained in:
@ -62,6 +62,12 @@ function loadDraft(store, opts) {
|
|||||||
|
|
||||||
const _popupMenuOptionsCallbacks = [];
|
const _popupMenuOptionsCallbacks = [];
|
||||||
|
|
||||||
|
let _checkDraftPopup = !Ember.testing;
|
||||||
|
|
||||||
|
export function toggleCheckDraftPopup(enabled) {
|
||||||
|
_checkDraftPopup = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
export function clearPopupMenuOptionsCallback() {
|
export function clearPopupMenuOptionsCallback() {
|
||||||
_popupMenuOptionsCallbacks.length = 0;
|
_popupMenuOptionsCallbacks.length = 0;
|
||||||
}
|
}
|
||||||
@ -770,23 +776,30 @@ export default Ember.Controller.extend({
|
|||||||
.then(resolve, reject);
|
.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) {
|
||||||
if (composerModel.get("action") !== opts.action) {
|
if (composerModel.get("action") !== opts.action) {
|
||||||
composerModel.setProperties({ unlistTopic: false, whisper: false });
|
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);
|
self._setModel(composerModel, opts);
|
||||||
resolve();
|
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() {
|
cancelComposer() {
|
||||||
return new Ember.RSVP.Promise(resolve => {
|
return new Ember.RSVP.Promise(resolve => {
|
||||||
if (this.get("model.hasMetaData") || this.get("model.replyDirty")) {
|
if (this.get("model.hasMetaData") || this.get("model.replyDirty")) {
|
||||||
|
@ -296,6 +296,10 @@ en:
|
|||||||
new_topic: "New topic draft"
|
new_topic: "New topic draft"
|
||||||
new_private_message: "New private message draft"
|
new_private_message: "New private message draft"
|
||||||
topic_reply: "Draft reply"
|
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:
|
topic_count_latest:
|
||||||
one: "See {{count}} new or updated topic"
|
one: "See {{count}} new or updated topic"
|
||||||
|
@ -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", {
|
acceptance("Composer", {
|
||||||
loggedIn: true,
|
loggedIn: true,
|
||||||
|
pretend(server, helper) {
|
||||||
|
server.get("/draft.json", () => {
|
||||||
|
return helper.response({
|
||||||
|
draft: null,
|
||||||
|
draft_sequence: 42
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
settings: {
|
settings: {
|
||||||
enable_whispers: true
|
enable_whispers: true
|
||||||
}
|
}
|
||||||
@ -527,49 +536,25 @@ QUnit.test(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
acceptance("Composer and uncategorized is not allowed", {
|
QUnit.test("Checks for existing draft", async assert => {
|
||||||
loggedIn: true,
|
toggleCheckDraftPopup(true);
|
||||||
settings: {
|
|
||||||
enable_whispers: true,
|
// prettier-ignore
|
||||||
allow_uncategorized_topics: false
|
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
|
||||||
QUnit.test("Disable body until category is selected", async assert => {
|
} ];
|
||||||
replaceCurrentUser({ admin: false, staff: false, trust_level: 1 });
|
});
|
||||||
|
|
||||||
await visit("/");
|
await visit("/t/internationalization-localization/280");
|
||||||
await click("#create-topic");
|
|
||||||
assert.ok(exists(".d-editor-input"), "the composer input is visible");
|
await click(".topic-post:eq(0) button.show-more-actions");
|
||||||
assert.ok(
|
await click(".topic-post:eq(0) button.edit");
|
||||||
exists(".title-input .popup-tip.bad.hide"),
|
|
||||||
"title errors are hidden by default"
|
assert.equal(find(".modal-body").text(), I18n.t("drafts.abandon.confirm"));
|
||||||
);
|
|
||||||
assert.ok(
|
await click(".modal-footer .btn.btn-default");
|
||||||
exists(".d-editor-textarea-wrapper .popup-tip.bad.hide"),
|
|
||||||
"body errors are hidden by default"
|
toggleCheckDraftPopup(false);
|
||||||
);
|
|
||||||
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"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
@ -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"
|
||||||
|
);
|
||||||
|
});
|
@ -41,10 +41,18 @@ QUnit.test("Viewing Summary", async assert => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("Viewing Drafts", 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");
|
await visit("/u/eviltrout/activity/drafts");
|
||||||
assert.ok(exists(".user-stream"), "has drafts stream");
|
assert.ok(exists(".user-stream"), "has drafts stream");
|
||||||
assert.ok(
|
assert.ok(
|
||||||
$(".user-stream .user-stream-item-draft-actions").length,
|
exists(".user-stream .user-stream-item-draft-actions"),
|
||||||
"has draft action buttons"
|
"has draft action buttons"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user