mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
UX: keep form template client state when shrinking/reopening the composer (#23858)
* UX: keep form template client state when shrinking/reopening the composer
This commit is contained in:
parent
563bff509a
commit
b8813e9759
@ -1,5 +1,16 @@
|
||||
import I18n from "I18n";
|
||||
|
||||
export function getFormTemplateObject(form) {
|
||||
const formData = new FormData(form);
|
||||
|
||||
const formObject = {};
|
||||
formData.forEach((value, key) => {
|
||||
formObject[key] = value;
|
||||
});
|
||||
|
||||
return formObject;
|
||||
}
|
||||
|
||||
export default function prepareFormTemplateData(form, formTemplate) {
|
||||
const labelMap = formTemplate.reduce((acc, field) => {
|
||||
acc[field.id] = field.attributes.label;
|
||||
|
@ -36,7 +36,9 @@ import { categoryBadgeHTML } from "discourse/helpers/category-link";
|
||||
import renderTags from "discourse/lib/render-tags";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import prepareFormTemplateData from "discourse/lib/form-template-validation";
|
||||
import prepareFormTemplateData, {
|
||||
getFormTemplateObject,
|
||||
} from "discourse/lib/form-template-validation";
|
||||
import DiscardDraftModal from "discourse/components/modal/discard-draft";
|
||||
import PostEnqueuedModal from "discourse/components/modal/post-enqueued";
|
||||
import { disableImplicitInjections } from "discourse/lib/implicit-injections";
|
||||
@ -172,6 +174,14 @@ export default class ComposerService extends Service {
|
||||
return this.model.category?.get("form_template_ids");
|
||||
}
|
||||
|
||||
get hasFormTemplate() {
|
||||
return (
|
||||
this.formTemplateIds?.length > 0 &&
|
||||
!this.get("model.replyingToTopic") &&
|
||||
!this.get("model.editingPost")
|
||||
);
|
||||
}
|
||||
|
||||
get formTemplateInitialValues() {
|
||||
return this._formTemplateInitialValues;
|
||||
}
|
||||
@ -726,7 +736,11 @@ export default class ComposerService extends Service {
|
||||
|
||||
const composer = this.model;
|
||||
|
||||
if (isEmpty(composer?.reply) && isEmpty(composer?.title)) {
|
||||
if (
|
||||
isEmpty(composer?.reply) &&
|
||||
isEmpty(composer?.title) &&
|
||||
!this.hasFormTemplate
|
||||
) {
|
||||
this.close();
|
||||
} else if (composer?.viewOpenOrFullscreen) {
|
||||
this.shrink();
|
||||
@ -923,21 +937,15 @@ export default class ComposerService extends Service {
|
||||
this.set("showPreview", false);
|
||||
}
|
||||
|
||||
if (this.siteSettings.experimental_form_templates) {
|
||||
if (
|
||||
this.formTemplateIds?.length > 0 &&
|
||||
!this.get("model.replyingToTopic") &&
|
||||
!this.get("model.editingPost")
|
||||
) {
|
||||
const formTemplateData = prepareFormTemplateData(
|
||||
document.querySelector("#form-template-form"),
|
||||
this.selectedFormTemplate
|
||||
);
|
||||
if (formTemplateData) {
|
||||
this.model.set("reply", formTemplateData);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (this.hasFormTemplate) {
|
||||
const formTemplateData = prepareFormTemplateData(
|
||||
document.querySelector("#form-template-form"),
|
||||
this.selectedFormTemplate
|
||||
);
|
||||
if (formTemplateData) {
|
||||
this.model.set("reply", formTemplateData);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1610,7 +1618,8 @@ export default class ComposerService extends Service {
|
||||
shrink() {
|
||||
if (
|
||||
this.get("model.replyDirty") ||
|
||||
(this.get("model.canEditTitle") && this.get("model.titleDirty"))
|
||||
(this.get("model.canEditTitle") && this.get("model.titleDirty")) ||
|
||||
this.hasFormTemplate
|
||||
) {
|
||||
this.collapse();
|
||||
} else {
|
||||
@ -1626,6 +1635,15 @@ export default class ComposerService extends Service {
|
||||
if (this.model.draftSaving) {
|
||||
this._saveDraftDebounce = discourseDebounce(this, this._saveDraft, 2000);
|
||||
} else {
|
||||
// This is a temporary solution to avoid losing the current form template state
|
||||
// until we have a proper draft system for these forms
|
||||
if (this.hasFormTemplate) {
|
||||
const form = document.querySelector("#form-template-form");
|
||||
if (form) {
|
||||
this.set("formTemplateInitialValues", getFormTemplateObject(form));
|
||||
}
|
||||
}
|
||||
|
||||
this._saveDraftPromise = this.model
|
||||
.saveDraft(this.currentUser)
|
||||
.finally(() => {
|
||||
@ -1723,6 +1741,9 @@ export default class ComposerService extends Service {
|
||||
document.activeElement?.blur();
|
||||
document.documentElement.style.removeProperty("--composer-height");
|
||||
this.setProperties({ model: null, lastValidatedAt: null });
|
||||
|
||||
// This is a temporary solution to reset the saved form template state while we don't store drafts
|
||||
this.set("formTemplateInitialValues", undefined);
|
||||
}
|
||||
|
||||
closeAutocomplete() {
|
||||
|
@ -0,0 +1,128 @@
|
||||
import { click, fillIn, visit } from "@ember/test-helpers";
|
||||
import { toggleCheckDraftPopup } from "discourse/services/composer";
|
||||
import { cloneJSON } from "discourse-common/lib/object";
|
||||
import TopicFixtures from "discourse/tests/fixtures/topic";
|
||||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import { test } from "qunit";
|
||||
|
||||
acceptance("Composer Form Template", function (needs) {
|
||||
needs.user({
|
||||
id: 5,
|
||||
username: "kris",
|
||||
whisperer: true,
|
||||
});
|
||||
needs.settings({
|
||||
experimental_form_templates: true,
|
||||
general_category_id: 1,
|
||||
default_composer_category: 1,
|
||||
});
|
||||
needs.site({
|
||||
can_tag_topics: true,
|
||||
categories: [
|
||||
{
|
||||
id: 1,
|
||||
name: "General",
|
||||
slug: "general",
|
||||
permission: 1,
|
||||
topic_template: null,
|
||||
form_template_ids: [1],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "test too",
|
||||
slug: "test-too",
|
||||
permission: 1,
|
||||
topic_template: "",
|
||||
},
|
||||
],
|
||||
});
|
||||
needs.pretender((server, helper) => {
|
||||
server.put("/u/kris.json", () => helper.response({ user: {} }));
|
||||
|
||||
server.get("/form-templates/1.json", () => {
|
||||
return helper.response({
|
||||
form_template: {
|
||||
name: "Testing",
|
||||
template: `- type: input
|
||||
id: full-name
|
||||
attributes:
|
||||
label: "Full name"
|
||||
description: "What is your full name?"
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "Description"`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
server.get("/posts/419", () => {
|
||||
return helper.response({ id: 419 });
|
||||
});
|
||||
server.get("/composer/mentions", () => {
|
||||
return helper.response({
|
||||
users: [],
|
||||
user_reasons: {},
|
||||
groups: { staff: { user_count: 30 } },
|
||||
group_reasons: {},
|
||||
max_users_notified_per_group_mention: 100,
|
||||
});
|
||||
});
|
||||
server.get("/t/960.json", () => {
|
||||
const topicList = cloneJSON(TopicFixtures["/t/9/1.json"]);
|
||||
topicList.post_stream.posts[2].post_type = 4;
|
||||
return helper.response(topicList);
|
||||
});
|
||||
});
|
||||
|
||||
needs.hooks.afterEach(() => toggleCheckDraftPopup(false));
|
||||
|
||||
test("Composer Form Template is shrank and reopened", async function (assert) {
|
||||
await visit("/");
|
||||
await click("#create-topic");
|
||||
|
||||
assert.strictEqual(selectKit(".category-chooser").header().value(), "1");
|
||||
|
||||
assert.ok(
|
||||
document.querySelector("#reply-control").classList.contains("open"),
|
||||
"reply control is open"
|
||||
);
|
||||
|
||||
await fillIn(".form-template-field__input[name='full-name']", "John Smith");
|
||||
|
||||
await fillIn(
|
||||
".form-template-field__textarea[name='description']",
|
||||
"Community manager"
|
||||
);
|
||||
|
||||
await click(".toggle-minimize");
|
||||
|
||||
assert.ok(
|
||||
document.querySelector("#reply-control").classList.contains("draft"),
|
||||
"reply control is minimized into draft mode"
|
||||
);
|
||||
|
||||
await click(".toggle-fullscreen");
|
||||
|
||||
assert.ok(
|
||||
document.querySelector("#reply-control").classList.contains("open"),
|
||||
"reply control is opened from draft mode"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelector(".form-template-field__input[name='full-name']")
|
||||
.value,
|
||||
"John Smith",
|
||||
"keeps the value of the input field when composer is re-opened from draft mode"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelector(
|
||||
".form-template-field__textarea[name='description']"
|
||||
).value,
|
||||
"Community manager",
|
||||
"keeps the value of the textarea field when composer is re-opened from draft mode"
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user