From 7cad69e6ef83dbe5f3f7f49d44a9d2ecb6c715b4 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotlarek Date: Fri, 22 Dec 2023 11:23:42 +1100 Subject: [PATCH] UX: File types site setting (#24976) New file types site setting with buttons allowing to easily add image/video/audio/document extensions to the list. --- .../admin/addon/components/site-setting.hbs | 1 + .../site-settings/file-types-list.gjs | 152 ++++++++++++++++++ .../admin/addon/mixins/setting-component.js | 6 + .../components/site-setting-test.js | 29 ++++ .../stylesheets/common/admin/settings.scss | 7 + config/locales/client.en.yml | 7 + config/locales/server.en.yml | 6 +- config/site_settings.yml | 6 +- 8 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 app/assets/javascripts/admin/addon/components/site-settings/file-types-list.gjs diff --git a/app/assets/javascripts/admin/addon/components/site-setting.hbs b/app/assets/javascripts/admin/addon/components/site-setting.hbs index 1e72fadac17..be6c7705313 100644 --- a/app/assets/javascripts/admin/addon/components/site-setting.hbs +++ b/app/assets/javascripts/admin/addon/components/site-setting.hbs @@ -33,6 +33,7 @@ preview=this.preview isSecret=this.isSecret allowAny=this.allowAny + changeValueCallback=this.changeValueCallback }} diff --git a/app/assets/javascripts/admin/addon/components/site-settings/file-types-list.gjs b/app/assets/javascripts/admin/addon/components/site-settings/file-types-list.gjs new file mode 100644 index 00000000000..2e8a5cb9304 --- /dev/null +++ b/app/assets/javascripts/admin/addon/components/site-settings/file-types-list.gjs @@ -0,0 +1,152 @@ +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { fn, hash } from "@ember/helper"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import { isEmpty } from "@ember/utils"; +import DButton from "discourse/components/d-button"; +import htmlSafe from "discourse-common/helpers/html-safe"; +import i18n from "discourse-common/helpers/i18n"; +import { makeArray } from "discourse-common/lib/helpers"; +import I18n from "discourse-i18n"; +import SettingValidationMessage from "admin/components/setting-validation-message"; +import ListSetting from "select-kit/components/list-setting"; + +const IMAGE_TYPES = [ + "gif", + "png", + "jpeg", + "jpg", + "heic", + "heif", + "webp", + "avif", + "svg", +]; +const VIDEO_TYPES = ["mov", "mp4", "webm", "m4v", "3gp", "ogv", "avi", "mpeg"]; +const AUDIO_TYPES = ["mp3", "ogg", "m4a", "wav", "aac", "flac"]; +const DOCUMENT_TYPES = ["txt", "pdf", "doc", "docx", "csv"]; +const TOKEN_SEPARATOR = "|"; +const IMAGE_TYPES_STRING = IMAGE_TYPES.join(", "); +const VIDEO_TYPES_STRING = VIDEO_TYPES.join(", "); +const AUDIO_TYPES_STRING = AUDIO_TYPES.join(", "); +const DOCUMENT_TYPES_STRING = DOCUMENT_TYPES.join(", "); + +export default class FileTypesList extends Component { + @service toasts; + + @tracked createdChoices = null; + + get settingValue() { + return this.args.value.toString().split(TOKEN_SEPARATOR).filter(Boolean); + } + + get settingChoices() { + return [ + ...new Set([ + ...makeArray(this.settingValue), + ...makeArray(this.args.setting.choices), + ...makeArray(this.createdChoices), + ]), + ]; + } + + @action + onChangeListSetting(value) { + this.args.changeValueCallback(value.join(TOKEN_SEPARATOR)); + } + + @action + onChangeChoices(choices) { + this.createdChoices = [ + ...new Set([...makeArray(this.createdChoices), ...makeArray(choices)]), + ]; + } + + @action + insertDefaultTypes(category) { + let types; + switch (category) { + case "image": + types = IMAGE_TYPES; + break; + case "video": + types = VIDEO_TYPES; + break; + case "audio": + types = AUDIO_TYPES; + break; + case "document": + types = DOCUMENT_TYPES; + break; + } + + const oldTypes = this.args.value.split(TOKEN_SEPARATOR); + const newTypes = [...new Set([...oldTypes, ...types])]; + const diffTypes = newTypes.filter((type) => !oldTypes.includes(type)); + + if (isEmpty(diffTypes)) { + return; + } + + this.toasts.success({ + data: { + message: I18n.t("admin.site_settings.file_types_list.add_types_toast", { + types: diffTypes.join(", "), + }), + }, + }); + + this.args.changeValueCallback(newTypes.join(TOKEN_SEPARATOR)); + } + +} diff --git a/app/assets/javascripts/admin/addon/mixins/setting-component.js b/app/assets/javascripts/admin/addon/mixins/setting-component.js index bb7b8e66317..e0a67e6fc58 100644 --- a/app/assets/javascripts/admin/addon/mixins/setting-component.js +++ b/app/assets/javascripts/admin/addon/mixins/setting-component.js @@ -34,6 +34,7 @@ const CUSTOM_TYPES = [ "emoji_list", "named_list", "file_size_restriction", + "file_types_list", ]; const AUTO_REFRESH_ON_SAVE = ["logo", "logo_small", "large_icon"]; @@ -230,6 +231,11 @@ export default Mixin.create({ } }, + @action + changeValueCallback(value) { + this.set("buffered.value", value); + }, + @action cancel() { this.rollbackBuffer(); diff --git a/app/assets/javascripts/discourse/tests/integration/components/site-setting-test.js b/app/assets/javascripts/discourse/tests/integration/components/site-setting-test.js index c7f3a090db3..c3f0cb99cf5 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/site-setting-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/site-setting-test.js @@ -59,4 +59,33 @@ module("Integration | Component | site-setting", function (hooks) { assert.strictEqual(query(".validation-error h1"), null); }); + + test("displays file types list setting", async function (assert) { + this.set("setting", { + setting: "theme_authorized_extensions", + value: "jpg|jpeg|png", + type: "file_types_list", + }); + + await render(hbs``); + + assert.strictEqual( + query(".formatted-selection").innerText, + "jpg, jpeg, png" + ); + + await click(query(".file-types-list__button.image")); + + assert.strictEqual( + query(".formatted-selection").innerText, + "jpg, jpeg, png, gif, heic, heif, webp, avif, svg" + ); + + await click(query(".file-types-list__button.image")); + + assert.strictEqual( + query(".formatted-selection").innerText, + "jpg, jpeg, png, gif, heic, heif, webp, avif, svg" + ); + }); }); diff --git a/app/assets/stylesheets/common/admin/settings.scss b/app/assets/stylesheets/common/admin/settings.scss index 99a27fb033e..fbcfd57441b 100644 --- a/app/assets/stylesheets/common/admin/settings.scss +++ b/app/assets/stylesheets/common/admin/settings.scss @@ -137,4 +137,11 @@ .warning { color: var(--danger); } + .file-types-list__button { + margin-top: 0.5em; + margin-bottom: 0.2em; + background: var(--primary-medium); + color: var(--secondary); + font-size: var(--font-down-2); + } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 8bde403c947..9c5d318a31a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -6373,6 +6373,13 @@ en: json_schema: edit: Launch Editor modal_title: "Edit %{name}" + file_types_list: + add_image_types: "Images" + add_video_types: "Videos" + add_audio_types: "Audio" + add_document_types: "Documents" + add_types_title: "Allow extensions %{types}" + add_types_toast: "%{types} file types added" badges: title: Badges diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 824680b4f1f..02db6742387 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2023,9 +2023,9 @@ en: max_image_size_kb: "The maximum image upload size. This must be configured in nginx (client_max_body_size) / apache or proxy as well. Images larger than this and smaller than client_max_body_size will be resized to fit on upload." max_attachment_size_kb: "The maximum attachment files upload size. This must be configured in nginx (client_max_body_size) / apache or proxy as well." - authorized_extensions: "A list of file extensions allowed for upload (use '*' to enable all file types)" - authorized_extensions_for_staff: "A list of file extensions allowed for upload for staff users in addition to the list defined in the `authorized_extensions` site setting. (use '*' to enable all file types)" - theme_authorized_extensions: "A list of file extensions allowed for theme uploads (use '*' to enable all file types)" + authorized_extensions: "A list of file extensions allowed for upload" + authorized_extensions_for_staff: "A list of file extensions allowed for upload for staff users in addition to the list defined in the `authorized_extensions` site setting." + theme_authorized_extensions: "A list of file extensions allowed for theme uploads" max_similar_results: "How many similar topics to show above the editor when composing a new topic. Comparison is based on title and body." max_image_megapixels: "Maximum number of megapixels allowed for an image. Images with a higher number of megapixels will be rejected." diff --git a/config/site_settings.yml b/config/site_settings.yml index 16a6c137530..5aeee803165 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1435,19 +1435,19 @@ files: theme_authorized_extensions: default: "wasm|jpg|jpeg|png|woff|woff2|svg|eot|ttf|otf|gif|webp|avif|js" type: list - list_type: compact + list_type: file_types authorized_extensions: client: true default: "jpg|jpeg|png|gif|heic|heif|webp|avif" refresh: true type: list - list_type: compact + list_type: file_types authorized_extensions_for_staff: client: true default: "" refresh: true type: list - list_type: compact + list_type: file_types export_authorized_extensions: hidden: true default: "zip"