From 64b8324e430a061b05afa8e82cd1d8c476fb108c Mon Sep 17 00:00:00 2001 From: Kelvin Tan Date: Wed, 28 May 2025 10:33:29 +0800 Subject: [PATCH 1/4] DEV: move setting-component mixin logic into site-setting component --- .../admin/addon/components/site-setting.gjs | 462 ++++++++++++++---- 1 file changed, 375 insertions(+), 87 deletions(-) diff --git a/app/assets/javascripts/admin/addon/components/site-setting.gjs b/app/assets/javascripts/admin/addon/components/site-setting.gjs index 23a7d728b747a..269fc587f8830 100644 --- a/app/assets/javascripts/admin/addon/components/site-setting.gjs +++ b/app/assets/javascripts/admin/addon/components/site-setting.gjs @@ -1,25 +1,66 @@ import { tracked } from "@glimmer/tracking"; -import Component from "@ember/component"; +import Component from "@glimmer/component"; +import { action } from "@ember/object"; import { hash } from "@ember/helper"; import { dependentKeyCompat } from "@ember/object/compat"; -import { readOnly } from "@ember/object/computed"; import { getOwner } from "@ember/owner"; import { LinkTo } from "@ember/routing"; +import { service } from "@ember/service"; +import { htmlSafe } from "@ember/template"; +import { isNone } from "@ember/utils"; +import { on } from "@ember/modifier"; import DButton from "discourse/components/d-button"; import icon from "discourse/helpers/d-icon"; import { i18n } from "discourse-i18n"; +import JsonSchemaEditorModal from "discourse/components/modal/json-schema-editor"; +import { deepEqual } from "discourse/lib/object"; +import { humanizedSettingName } from "discourse/lib/site-settings-utils"; +import { splitString } from "discourse/lib/utilities"; import SettingValidationMessage from "admin/components/setting-validation-message"; import Description from "admin/components/site-settings/description"; -import SettingComponent from "admin/mixins/setting-component"; import SiteSetting from "admin/models/site-setting"; -export default class SiteSettingComponent extends Component.extend( - SettingComponent -) { +const CUSTOM_TYPES = [ + "bool", + "integer", + "enum", + "list", + "url_list", + "host_list", + "category_list", + "value_list", + "category", + "uploaded_image_list", + "compact_list", + "secret_list", + "upload", + "group_list", + "tag_list", + "tag_group_list", + "color", + "simple_list", + "emoji_list", + "named_list", + "file_size_restriction", + "file_types_list", + "font_list" +]; + +export default class SiteSettingComponent extends Component { + @service modal; + @service router; + @service site; + @service dialog; + @service siteSettingChangeTracker; + @tracked setting = null; + @tracked isSecret = null; updateExistingUsers = null; - @readOnly("setting.staffLogFilter") staffLogFilter; + constructor() { + super(...arguments); + this.isSecret = this.setting?.secret; + } get resolvedComponent() { return getOwner(this).resolveRegistration( @@ -32,100 +73,347 @@ export default class SiteSettingComponent extends Component.extend( return this.setting.buffered; } + get componentName() { + return `site-settings/${this.typeClass}`; + } + + get overridden() { + return this.setting.default !== this.buffered.value; + } + + get displayDescription() { + return this.componentType !== "bool"; + } + + get dirty() { + let bufferVal = this.buffered.value; + let settingVal = this.setting?.value; + + if (isNone(bufferVal)) { + bufferVal = ""; + } + + if (isNone(settingVal)) { + settingVal = ""; + } + + const dirty = !deepEqual(bufferVal, settingVal); + + if (dirty) { + this.siteSettingChangeTracker.add(this.setting); + } else { + this.siteSettingChangeTracker.remove(this.setting); + } + + return dirty; + } + + get preview() { + const setting = this.setting; + const value = this.buffered.value; + const preview = setting.preview; + if (preview) { + const escapedValue = preview.replace(/\{\{value\}\}/g, value); + return htmlSafe(`
${escapedValue}
`); + } + return null; + } + + get typeClass() { + const componentType = this.componentType; + return componentType.replace(/\_/g, "-"); + } + + get settingName() { + return humanizedSettingName(this.setting.setting, this.setting.label); + } + + get componentType() { + const type = this.type; + return CUSTOM_TYPES.includes(type) ? type : "string"; + } + + get type() { + const setting = this.setting; + if (setting.type === "list" && setting.list_type) { + return `${setting.list_type}_list`; + } + return setting.type; + } + + get allowAny() { + const anyValue = this.setting?.anyValue; + return anyValue !== false; + } + + get bufferedValues() { + const value = this.buffered.value; + return splitString(value, "|"); + } + + get defaultValues() { + const value = this.setting?.defaultValues; + return splitString(value, "|"); + } + + get defaultIsAvailable() { + const defaultValues = this.defaultValues; + const bufferedValues = this.bufferedValues; + return ( + defaultValues.length > 0 && + !defaultValues.every((value) => bufferedValues.includes(value)) + ); + } + + get settingEditButton() { + const setting = this.setting; + if (setting.json_schema) { + return { + action: () => { + this.modal.show(JsonSchemaEditorModal, { + model: { + updateValue: (value) => { + this.buffered.value = value; + }, + value: this.buffered.value, + settingName: setting.setting, + jsonSchema: setting.json_schema + } + }); + }, + label: "admin.site_settings.json_schema.edit", + icon: "pencil" + }; + } else if (setting.schema) { + return { + action: () => { + this.router.transitionTo("admin.schema", setting.setting); + }, + label: "admin.site_settings.json_schema.edit", + icon: "pencil" + }; + } else if (setting.objects_schema) { + return { + action: () => { + this.router.transitionTo( + "adminCustomizeThemes.show.schema", + setting.setting + ); + }, + label: "admin.customize.theme.edit_objects_theme_setting", + icon: "pencil" + }; + } + return null; + } + + get disableControls() { + return !!this.setting.isSaving; + } + + get staffLogFilter() { + return this.setting.staffLogFilter; + } + + @action + async update() { + if (this.setting.requiresConfirmation) { + const confirm = await this.siteSettingChangeTracker.confirmChanges( + this.setting + ); + + if (!confirm) { + return; + } + } + + if (this.setting.affectsExistingUsers) { + await this.siteSettingChangeTracker.configureBackfill(this.setting); + } + + await this.save(); + } + + @action + async save() { + try { + this.setting.isSaving = true; + + await this._save(); + + this.setting.validationMessage = null; + this.buffered.applyChanges(); + + if (this.setting.requiresReload) { + this.siteSettingChangeTracker.refreshPage({ + [this.setting.setting]: this.setting.value + }); + } + } catch (e) { + const json = e.jqXHR?.responseJSON; + if (json?.errors) { + let errorString = json.errors[0]; + + if (json.html_message) { + errorString = htmlSafe(errorString); + } + + this.setting.validationMessage = errorString; + } else { + this.setting.validationMessage = i18n("generic_error"); + } + } finally { + this.setting.isSaving = false; + } + } + + @action + changeValueCallback(value) { + this.buffered.value = value; + } + + @action + setValidationMessage(message) { + this.setting.validationMessage = message; + } + + @action + cancel() { + this.buffered.discardChanges(); + this.setting.validationMessage = null; + } + + @action + resetDefault() { + this.buffered.value = this.setting.default; + this.setting.validationMessage = null; + } + + @action + toggleSecret() { + this.isSecret = !this.isSecret; + } + + @action + setDefaultValues() { + this.buffered.value = this.bufferedValues + .concat(this.defaultValues) + .uniq() + .join("|"); + this.setting.validationMessage = null; + return false; + } + + @action + _handleKeydown(event) { + if ( + event.key === "Enter" && + event.target.classList.contains("input-setting-string") + ) { + this.save(); + } + } + _save() { const setting = this.buffered; - return SiteSetting.update(setting.get("setting"), setting.get("value"), { - updateExistingUsers: this.setting.updateExistingUsers, + return SiteSetting.update(setting.setting, setting.value, { + updateExistingUsers: this.setting.updateExistingUsers }); } } From d208fdcf506121e4518336ba6ed29734f8cf2793 Mon Sep 17 00:00:00 2001 From: Kelvin Tan Date: Wed, 28 May 2025 13:07:59 +0800 Subject: [PATCH 2/4] revert to ember classic component --- .../admin/addon/components/site-setting.gjs | 220 +++++++++--------- 1 file changed, 113 insertions(+), 107 deletions(-) diff --git a/app/assets/javascripts/admin/addon/components/site-setting.gjs b/app/assets/javascripts/admin/addon/components/site-setting.gjs index 269fc587f8830..6839bada25f85 100644 --- a/app/assets/javascripts/admin/addon/components/site-setting.gjs +++ b/app/assets/javascripts/admin/addon/components/site-setting.gjs @@ -1,21 +1,20 @@ import { tracked } from "@glimmer/tracking"; -import Component from "@glimmer/component"; -import { action } from "@ember/object"; +import Component from "@ember/component"; import { hash } from "@ember/helper"; +import { action } from "@ember/object"; import { dependentKeyCompat } from "@ember/object/compat"; import { getOwner } from "@ember/owner"; import { LinkTo } from "@ember/routing"; import { service } from "@ember/service"; import { htmlSafe } from "@ember/template"; import { isNone } from "@ember/utils"; -import { on } from "@ember/modifier"; import DButton from "discourse/components/d-button"; -import icon from "discourse/helpers/d-icon"; -import { i18n } from "discourse-i18n"; import JsonSchemaEditorModal from "discourse/components/modal/json-schema-editor"; +import icon from "discourse/helpers/d-icon"; import { deepEqual } from "discourse/lib/object"; import { humanizedSettingName } from "discourse/lib/site-settings-utils"; import { splitString } from "discourse/lib/utilities"; +import { i18n } from "discourse-i18n"; import SettingValidationMessage from "admin/components/setting-validation-message"; import Description from "admin/components/site-settings/description"; import SiteSetting from "admin/models/site-setting"; @@ -43,7 +42,7 @@ const CUSTOM_TYPES = [ "named_list", "file_size_restriction", "file_types_list", - "font_list" + "font_list", ]; export default class SiteSettingComponent extends Component { @@ -57,11 +56,34 @@ export default class SiteSettingComponent extends Component { @tracked isSecret = null; updateExistingUsers = null; + // Classic component attributes + classNameBindings = [":row", ":setting", "overridden", "typeClass"]; + attributeBindings = ["setting.setting:data-setting"]; + + _handleKeydown = (event) => { + if ( + event.key === "Enter" && + event.target.classList.contains("input-setting-string") + ) { + this.save(); + } + }; + constructor() { super(...arguments); this.isSecret = this.setting?.secret; } + didInsertElement() { + super.didInsertElement(...arguments); + this.element.addEventListener("keydown", this._handleKeydown); + } + + willDestroyElement() { + super.willDestroyElement(...arguments); + this.element.removeEventListener("keydown", this._handleKeydown); + } + get resolvedComponent() { return getOwner(this).resolveRegistration( `component:${this.componentName}` @@ -177,12 +199,12 @@ export default class SiteSettingComponent extends Component { }, value: this.buffered.value, settingName: setting.setting, - jsonSchema: setting.json_schema - } + jsonSchema: setting.json_schema, + }, }); }, label: "admin.site_settings.json_schema.edit", - icon: "pencil" + icon: "pencil", }; } else if (setting.schema) { return { @@ -190,7 +212,7 @@ export default class SiteSettingComponent extends Component { this.router.transitionTo("admin.schema", setting.setting); }, label: "admin.site_settings.json_schema.edit", - icon: "pencil" + icon: "pencil", }; } else if (setting.objects_schema) { return { @@ -201,7 +223,7 @@ export default class SiteSettingComponent extends Component { ); }, label: "admin.customize.theme.edit_objects_theme_setting", - icon: "pencil" + icon: "pencil", }; } return null; @@ -246,7 +268,7 @@ export default class SiteSettingComponent extends Component { if (this.setting.requiresReload) { this.siteSettingChangeTracker.refreshPage({ - [this.setting.setting]: this.setting.value + [this.setting.setting]: this.setting.value, }); } } catch (e) { @@ -304,116 +326,100 @@ export default class SiteSettingComponent extends Component { return false; } - @action - _handleKeydown(event) { - if ( - event.key === "Enter" && - event.target.classList.contains("input-setting-string") - ) { - this.save(); - } - } - _save() { const setting = this.buffered; return SiteSetting.update(setting.setting, setting.value, { - updateExistingUsers: this.setting.updateExistingUsers + updateExistingUsers: this.setting.updateExistingUsers, }); } } From 4d777da563288e817f556f006b02b09e84237a3b Mon Sep 17 00:00:00 2001 From: Kelvin Tan Date: Wed, 28 May 2025 13:50:48 +0800 Subject: [PATCH 3/4] use get/set on buffered and change handleKeyDown to async --- .../admin/addon/components/site-setting.gjs | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/admin/addon/components/site-setting.gjs b/app/assets/javascripts/admin/addon/components/site-setting.gjs index 6839bada25f85..ee048a1f2871e 100644 --- a/app/assets/javascripts/admin/addon/components/site-setting.gjs +++ b/app/assets/javascripts/admin/addon/components/site-setting.gjs @@ -42,7 +42,7 @@ const CUSTOM_TYPES = [ "named_list", "file_size_restriction", "file_types_list", - "font_list", + "font_list" ]; export default class SiteSettingComponent extends Component { @@ -60,12 +60,12 @@ export default class SiteSettingComponent extends Component { classNameBindings = [":row", ":setting", "overridden", "typeClass"]; attributeBindings = ["setting.setting:data-setting"]; - _handleKeydown = (event) => { + _handleKeydown = async (event) => { if ( event.key === "Enter" && event.target.classList.contains("input-setting-string") ) { - this.save(); + await this.save(); } }; @@ -100,7 +100,7 @@ export default class SiteSettingComponent extends Component { } get overridden() { - return this.setting.default !== this.buffered.value; + return this.setting.default !== this.buffered.get("value"); } get displayDescription() { @@ -108,7 +108,7 @@ export default class SiteSettingComponent extends Component { } get dirty() { - let bufferVal = this.buffered.value; + let bufferVal = this.buffered.get("value"); let settingVal = this.setting?.value; if (isNone(bufferVal)) { @@ -132,7 +132,7 @@ export default class SiteSettingComponent extends Component { get preview() { const setting = this.setting; - const value = this.buffered.value; + const value = this.buffered.get("value"); const preview = setting.preview; if (preview) { const escapedValue = preview.replace(/\{\{value\}\}/g, value); @@ -169,7 +169,7 @@ export default class SiteSettingComponent extends Component { } get bufferedValues() { - const value = this.buffered.value; + const value = this.buffered.get("value"); return splitString(value, "|"); } @@ -195,16 +195,16 @@ export default class SiteSettingComponent extends Component { this.modal.show(JsonSchemaEditorModal, { model: { updateValue: (value) => { - this.buffered.value = value; + this.buffered.set("value", value); }, - value: this.buffered.value, + value: this.buffered.get("value"), settingName: setting.setting, - jsonSchema: setting.json_schema, - }, + jsonSchema: setting.json_schema + } }); }, label: "admin.site_settings.json_schema.edit", - icon: "pencil", + icon: "pencil" }; } else if (setting.schema) { return { @@ -212,7 +212,7 @@ export default class SiteSettingComponent extends Component { this.router.transitionTo("admin.schema", setting.setting); }, label: "admin.site_settings.json_schema.edit", - icon: "pencil", + icon: "pencil" }; } else if (setting.objects_schema) { return { @@ -223,7 +223,7 @@ export default class SiteSettingComponent extends Component { ); }, label: "admin.customize.theme.edit_objects_theme_setting", - icon: "pencil", + icon: "pencil" }; } return null; @@ -268,7 +268,7 @@ export default class SiteSettingComponent extends Component { if (this.setting.requiresReload) { this.siteSettingChangeTracker.refreshPage({ - [this.setting.setting]: this.setting.value, + [this.setting.setting]: this.setting.value }); } } catch (e) { @@ -291,7 +291,7 @@ export default class SiteSettingComponent extends Component { @action changeValueCallback(value) { - this.buffered.value = value; + this.buffered.set("value", value); } @action @@ -307,7 +307,7 @@ export default class SiteSettingComponent extends Component { @action resetDefault() { - this.buffered.value = this.setting.default; + this.buffered.set("value", this.setting.default); this.setting.validationMessage = null; } @@ -318,18 +318,21 @@ export default class SiteSettingComponent extends Component { @action setDefaultValues() { - this.buffered.value = this.bufferedValues - .concat(this.defaultValues) - .uniq() - .join("|"); + this.buffered.set( + "value", + this.bufferedValues + .concat(this.defaultValues) + .uniq() + .join("|") + ); this.setting.validationMessage = null; return false; } _save() { const setting = this.buffered; - return SiteSetting.update(setting.setting, setting.value, { - updateExistingUsers: this.setting.updateExistingUsers, + return SiteSetting.update(setting.get("setting"), setting.get("value"), { + updateExistingUsers: this.setting.updateExistingUsers }); } From 102b42f32746c7f7255a39d7cbf593990cd29131 Mon Sep 17 00:00:00 2001 From: Kelvin Tan Date: Fri, 30 May 2025 11:58:34 +0800 Subject: [PATCH 4/4] address comments --- .../admin/addon/components/site-setting.gjs | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/admin/addon/components/site-setting.gjs b/app/assets/javascripts/admin/addon/components/site-setting.gjs index ee048a1f2871e..f2036db2b5ba9 100644 --- a/app/assets/javascripts/admin/addon/components/site-setting.gjs +++ b/app/assets/javascripts/admin/addon/components/site-setting.gjs @@ -42,13 +42,12 @@ const CUSTOM_TYPES = [ "named_list", "file_size_restriction", "file_types_list", - "font_list" + "font_list", ]; export default class SiteSettingComponent extends Component { @service modal; @service router; - @service site; @service dialog; @service siteSettingChangeTracker; @@ -60,15 +59,6 @@ export default class SiteSettingComponent extends Component { classNameBindings = [":row", ":setting", "overridden", "typeClass"]; attributeBindings = ["setting.setting:data-setting"]; - _handleKeydown = async (event) => { - if ( - event.key === "Enter" && - event.target.classList.contains("input-setting-string") - ) { - await this.save(); - } - }; - constructor() { super(...arguments); this.isSecret = this.setting?.secret; @@ -84,6 +74,16 @@ export default class SiteSettingComponent extends Component { this.element.removeEventListener("keydown", this._handleKeydown); } + @action + async _handleKeydown(event) { + if ( + event.key === "Enter" && + event.target.classList.contains("input-setting-string") + ) { + await this.save(); + } + } + get resolvedComponent() { return getOwner(this).resolveRegistration( `component:${this.componentName}` @@ -199,12 +199,12 @@ export default class SiteSettingComponent extends Component { }, value: this.buffered.get("value"), settingName: setting.setting, - jsonSchema: setting.json_schema - } + jsonSchema: setting.json_schema, + }, }); }, label: "admin.site_settings.json_schema.edit", - icon: "pencil" + icon: "pencil", }; } else if (setting.schema) { return { @@ -212,7 +212,7 @@ export default class SiteSettingComponent extends Component { this.router.transitionTo("admin.schema", setting.setting); }, label: "admin.site_settings.json_schema.edit", - icon: "pencil" + icon: "pencil", }; } else if (setting.objects_schema) { return { @@ -223,7 +223,7 @@ export default class SiteSettingComponent extends Component { ); }, label: "admin.customize.theme.edit_objects_theme_setting", - icon: "pencil" + icon: "pencil", }; } return null; @@ -268,7 +268,7 @@ export default class SiteSettingComponent extends Component { if (this.setting.requiresReload) { this.siteSettingChangeTracker.refreshPage({ - [this.setting.setting]: this.setting.value + [this.setting.setting]: this.setting.value, }); } } catch (e) { @@ -320,19 +320,15 @@ export default class SiteSettingComponent extends Component { setDefaultValues() { this.buffered.set( "value", - this.bufferedValues - .concat(this.defaultValues) - .uniq() - .join("|") + this.bufferedValues.concat(this.defaultValues).uniq().join("|") ); this.setting.validationMessage = null; - return false; } _save() { const setting = this.buffered; return SiteSetting.update(setting.get("setting"), setting.get("value"), { - updateExistingUsers: this.setting.updateExistingUsers + updateExistingUsers: this.setting.updateExistingUsers, }); }