discourse/app/assets/javascripts/admin/addon/mixins/setting-component.js
Martin Brennan 27bdfb6437
Some checks are pending
Licenses / run (push) Waiting to run
Linting / run (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (annotations, core) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (backend, core) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (backend, plugins) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (frontend, plugins) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (frontend, themes) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, chat) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, core) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, plugins) (push) Waiting to run
Tests / ${{ matrix.target }} ${{ matrix.build_type }} (system, themes) (push) Waiting to run
Tests / core frontend (${{ matrix.browser }}) (Chrome) (push) Waiting to run
Tests / core frontend (${{ matrix.browser }}) (Firefox ESR) (push) Waiting to run
Tests / core frontend (${{ matrix.browser }}) (Firefox Evergreen) (push) Waiting to run
FEATURE: Add user preference to disable smart lists (#29434)
Followup 30fdd7738e

Adds a new site setting and corresponding user preference
to disable smart lists. By default they are enabled, because
this is a better experience for most users. A small number of
users would prefer to not have this enabled.

Smart lists automatically append new items to each
list started in the composer when enter is pressed. If
enter is pressed on an empty list item, it is cleared.

This setting will be removed when the new composer is complete.
2024-10-28 14:18:15 +10:00

378 lines
10 KiB
JavaScript

import { warn } from "@ember/debug";
import { action, computed } from "@ember/object";
import { alias, oneWay } from "@ember/object/computed";
import Mixin from "@ember/object/mixin";
import { service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import { isNone } from "@ember/utils";
import { Promise } from "rsvp";
import JsonSchemaEditorModal from "discourse/components/modal/json-schema-editor";
import { ajax } from "discourse/lib/ajax";
import { fmt, propertyNotEqual } from "discourse/lib/computed";
import { SITE_SETTING_REQUIRES_CONFIRMATION_TYPES } from "discourse/lib/constants";
import { splitString } from "discourse/lib/utilities";
import { deepEqual } from "discourse-common/lib/object";
import I18n from "discourse-i18n";
import SiteSettingDefaultCategoriesModal from "../components/modal/site-setting-default-categories";
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",
];
const AUTO_REFRESH_ON_SAVE = ["logo", "logo_small", "large_icon"];
const DEFAULT_USER_PREFERENCES = [
"default_email_digest_frequency",
"default_include_tl0_in_digests",
"default_email_level",
"default_email_messages_level",
"default_email_mailing_list_mode",
"default_email_mailing_list_mode_frequency",
"default_email_previous_replies",
"default_email_in_reply_to",
"default_hide_profile_and_presence",
"default_other_new_topic_duration_minutes",
"default_other_auto_track_topics_after_msecs",
"default_other_notification_level_when_replying",
"default_other_external_links_in_new_tab",
"default_other_enable_quoting",
"default_other_enable_smart_lists",
"default_other_enable_defer",
"default_other_dynamic_favicon",
"default_other_like_notification_frequency",
"default_other_skip_new_user_tips",
"default_topics_automatic_unpin",
"default_categories_watching",
"default_categories_tracking",
"default_categories_muted",
"default_categories_watching_first_post",
"default_categories_normal",
"default_tags_watching",
"default_tags_tracking",
"default_tags_muted",
"default_tags_watching_first_post",
"default_text_size",
"default_title_count_mode",
"default_navigation_menu_categories",
"default_navigation_menu_tags",
"default_sidebar_link_to_filtered_list",
"default_sidebar_show_count_of_new_items",
];
export default Mixin.create({
modal: service(),
router: service(),
site: service(),
dialog: service(),
attributeBindings: ["setting.setting:data-setting"],
classNameBindings: [":row", ":setting", "overridden", "typeClass"],
validationMessage: null,
setting: null,
content: alias("setting"),
isSecret: oneWay("setting.secret"),
componentName: fmt("typeClass", "site-settings/%@"),
overridden: propertyNotEqual("setting.default", "buffered.value"),
didInsertElement() {
this._super(...arguments);
this.element.addEventListener("keydown", this._handleKeydown);
},
willDestroyElement() {
this._super(...arguments);
this.element.removeEventListener("keydown", this._handleKeydown);
},
displayDescription: computed("componentType", function () {
return this.componentType !== "bool";
}),
dirty: computed("buffered.value", "setting.value", function () {
let bufferVal = this.get("buffered.value");
let settingVal = this.setting?.value;
if (isNone(bufferVal)) {
bufferVal = "";
}
if (isNone(settingVal)) {
settingVal = "";
}
return !deepEqual(bufferVal, settingVal);
}),
preview: computed("setting", "buffered.value", function () {
const setting = this.setting;
const value = this.get("buffered.value");
const preview = setting.preview;
if (preview) {
const escapedValue = preview.replace(/\{\{value\}\}/g, value);
return htmlSafe(`<div class='preview'>${escapedValue}</div>`);
}
}),
typeClass: computed("componentType", function () {
const componentType = this.componentType;
return componentType.replace(/\_/g, "-");
}),
settingName: computed("setting.setting", "setting.label", function () {
const setting = this.setting?.setting;
const label = this.setting?.label;
return label || setting.replace(/\_/g, " ");
}),
componentType: computed("type", function () {
const type = this.type;
return CUSTOM_TYPES.includes(type) ? type : "string";
}),
type: computed("setting", function () {
const setting = this.setting;
if (setting.type === "list" && setting.list_type) {
return `${setting.list_type}_list`;
}
return setting.type;
}),
allowAny: computed("setting.anyValue", function () {
const anyValue = this.setting?.anyValue;
return anyValue !== false;
}),
bufferedValues: computed("buffered.value", function () {
const value = this.get("buffered.value");
return splitString(value, "|");
}),
defaultValues: computed("setting.defaultValues", function () {
const value = this.setting?.defaultValues;
return splitString(value, "|");
}),
defaultIsAvailable: computed("defaultValues", "bufferedValues", function () {
const defaultValues = this.defaultValues;
const bufferedValues = this.bufferedValues;
return (
defaultValues.length > 0 &&
!defaultValues.every((value) => bufferedValues.includes(value))
);
}),
settingEditButton: computed("setting", function () {
const setting = this.setting;
if (setting.json_schema) {
return {
action: () => {
this.modal.show(JsonSchemaEditorModal, {
model: {
updateValue: (value) => {
this.buffered.set("value", value);
},
value: this.buffered.get("value"),
settingName: setting.setting,
jsonSchema: setting.json_schema,
},
});
},
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",
};
}
}),
disableSaveButton: computed("validationMessage", function () {
return !!this.validationMessage;
}),
confirmChanges(settingKey) {
return new Promise((resolve) => {
// Fallback is needed in case the setting does not have a custom confirmation
// prompt/confirm defined.
this.dialog.alert({
message: I18n.t(
`admin.site_settings.requires_confirmation_messages.${settingKey}.prompt`,
{
translatedFallback: I18n.t(
"admin.site_settings.requires_confirmation_messages.default.prompt"
),
}
),
buttons: [
{
label: I18n.t(
`admin.site_settings.requires_confirmation_messages.${settingKey}.confirm`,
{
translatedFallback: I18n.t(
"admin.site_settings.requires_confirmation_messages.default.confirm"
),
}
),
class: "btn-primary",
action: () => resolve(true),
},
{
label: I18n.t("no_value"),
class: "btn-default",
action: () => resolve(false),
},
],
});
});
},
update: action(async function () {
const key = this.buffered.get("setting");
let confirm = true;
if (
this.buffered.get("requires_confirmation") ===
SITE_SETTING_REQUIRES_CONFIRMATION_TYPES.simple
) {
confirm = await this.confirmChanges(key);
}
if (!confirm) {
this.cancel();
return;
}
if (!DEFAULT_USER_PREFERENCES.includes(key)) {
await this.save();
return;
}
const data = {
[key]: this.buffered.get("value"),
};
const result = await ajax(`/admin/site_settings/${key}/user_count.json`, {
type: "PUT",
data,
});
const count = result.user_count;
if (count > 0) {
await this.modal.show(SiteSettingDefaultCategoriesModal, {
model: {
siteSetting: { count, key: key.replaceAll("_", " ") },
setUpdateExistingUsers: this.setUpdateExistingUsers,
},
});
this.save();
} else {
await this.save();
}
}),
setUpdateExistingUsers: action(function (value) {
this.updateExistingUsers = value;
}),
save: action(async function () {
try {
await this._save();
this.set("validationMessage", null);
this.commitBuffer();
if (AUTO_REFRESH_ON_SAVE.includes(this.setting.setting)) {
this.afterSave();
}
} catch (e) {
const json = e.jqXHR?.responseJSON;
if (json?.errors) {
let errorString = json.errors[0];
if (json.html_message) {
errorString = htmlSafe(errorString);
}
this.set("validationMessage", errorString);
} else {
this.set("validationMessage", I18n.t("generic_error"));
}
}
}),
changeValueCallback: action(function (value) {
this.set("buffered.value", value);
}),
setValidationMessage: action(function (message) {
this.set("validationMessage", message);
}),
cancel: action(function () {
this.rollbackBuffer();
this.set("validationMessage", null);
}),
resetDefault: action(function () {
this.set("buffered.value", this.setting.default);
this.set("validationMessage", null);
}),
toggleSecret: action(function () {
this.toggleProperty("isSecret");
}),
setDefaultValues: action(function () {
this.set(
"buffered.value",
this.bufferedValues.concat(this.defaultValues).uniq().join("|")
);
this.set("validationMessage", null);
return false;
}),
_handleKeydown: action(function (event) {
if (
event.key === "Enter" &&
event.target.classList.contains("input-setting-string")
) {
this.save();
}
}),
async _save() {
warn("You should define a `_save` method", {
id: "discourse.setting-component.missing-save",
});
},
});