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:
Renato Atilio 2023-10-10 16:21:06 -03:00 committed by GitHub
parent 563bff509a
commit b8813e9759
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 178 additions and 18 deletions

View File

@ -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;

View File

@ -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() {

View File

@ -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"
);
});
});