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.
This commit is contained in:
parent
fe8b1a1074
commit
7cad69e6ef
|
@ -33,6 +33,7 @@
|
||||||
preview=this.preview
|
preview=this.preview
|
||||||
isSecret=this.isSecret
|
isSecret=this.isSecret
|
||||||
allowAny=this.allowAny
|
allowAny=this.allowAny
|
||||||
|
changeValueCallback=this.changeValueCallback
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
<template>
|
||||||
|
<ListSetting
|
||||||
|
@value={{this.settingValue}}
|
||||||
|
@settingName={{@setting.setting}}
|
||||||
|
@choices={{this.settingChoices}}
|
||||||
|
@onChange={{this.onChangeListSetting}}
|
||||||
|
@onChangeChoices={{this.onChangeChoices}}
|
||||||
|
@options={{hash allowAny=@allowAny}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DButton
|
||||||
|
@action={{(fn this.insertDefaultTypes "image")}}
|
||||||
|
@label="admin.site_settings.file_types_list.add_image_types"
|
||||||
|
@translatedTitle={{i18n
|
||||||
|
"admin.site_settings.file_types_list.add_types_title"
|
||||||
|
types=IMAGE_TYPES_STRING
|
||||||
|
}}
|
||||||
|
class="btn file-types-list__button image"
|
||||||
|
/>
|
||||||
|
<DButton
|
||||||
|
@action={{(fn this.insertDefaultTypes "video")}}
|
||||||
|
@label="admin.site_settings.file_types_list.add_video_types"
|
||||||
|
@translatedTitle={{i18n
|
||||||
|
"admin.site_settings.file_types_list.add_types_title"
|
||||||
|
types=VIDEO_TYPES_STRING
|
||||||
|
}}
|
||||||
|
class="btn file-types-list__button video"
|
||||||
|
/>
|
||||||
|
<DButton
|
||||||
|
@action={{(fn this.insertDefaultTypes "audio")}}
|
||||||
|
@label="admin.site_settings.file_types_list.add_audio_types"
|
||||||
|
@translatedTitle={{i18n
|
||||||
|
"admin.site_settings.file_types_list.add_types_title audio"
|
||||||
|
types=AUDIO_TYPES_STRING
|
||||||
|
}}
|
||||||
|
class="btn file-types-list__button"
|
||||||
|
/>
|
||||||
|
<DButton
|
||||||
|
@action={{(fn this.insertDefaultTypes "document")}}
|
||||||
|
@label="admin.site_settings.file_types_list.add_document_types"
|
||||||
|
@translatedTitle={{i18n
|
||||||
|
"admin.site_settings.file_types_list.add_types_title"
|
||||||
|
types=DOCUMENT_TYPES_STRING
|
||||||
|
}}
|
||||||
|
class="btn file-types-list__button document"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingValidationMessage @message={{this.validationMessage}} />
|
||||||
|
<div class="desc">{{htmlSafe @setting.description}}</div>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ const CUSTOM_TYPES = [
|
||||||
"emoji_list",
|
"emoji_list",
|
||||||
"named_list",
|
"named_list",
|
||||||
"file_size_restriction",
|
"file_size_restriction",
|
||||||
|
"file_types_list",
|
||||||
];
|
];
|
||||||
|
|
||||||
const AUTO_REFRESH_ON_SAVE = ["logo", "logo_small", "large_icon"];
|
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
|
@action
|
||||||
cancel() {
|
cancel() {
|
||||||
this.rollbackBuffer();
|
this.rollbackBuffer();
|
||||||
|
|
|
@ -59,4 +59,33 @@ module("Integration | Component | site-setting", function (hooks) {
|
||||||
|
|
||||||
assert.strictEqual(query(".validation-error h1"), null);
|
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`<SiteSetting @setting={{this.setting}} />`);
|
||||||
|
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -137,4 +137,11 @@
|
||||||
.warning {
|
.warning {
|
||||||
color: var(--danger);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6373,6 +6373,13 @@ en:
|
||||||
json_schema:
|
json_schema:
|
||||||
edit: Launch Editor
|
edit: Launch Editor
|
||||||
modal_title: "Edit %{name}"
|
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:
|
badges:
|
||||||
title: Badges
|
title: Badges
|
||||||
|
|
|
@ -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_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."
|
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: "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. (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."
|
||||||
theme_authorized_extensions: "A list of file extensions allowed for theme uploads (use '*' to enable all file types)"
|
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_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."
|
max_image_megapixels: "Maximum number of megapixels allowed for an image. Images with a higher number of megapixels will be rejected."
|
||||||
|
|
|
@ -1435,19 +1435,19 @@ files:
|
||||||
theme_authorized_extensions:
|
theme_authorized_extensions:
|
||||||
default: "wasm|jpg|jpeg|png|woff|woff2|svg|eot|ttf|otf|gif|webp|avif|js"
|
default: "wasm|jpg|jpeg|png|woff|woff2|svg|eot|ttf|otf|gif|webp|avif|js"
|
||||||
type: list
|
type: list
|
||||||
list_type: compact
|
list_type: file_types
|
||||||
authorized_extensions:
|
authorized_extensions:
|
||||||
client: true
|
client: true
|
||||||
default: "jpg|jpeg|png|gif|heic|heif|webp|avif"
|
default: "jpg|jpeg|png|gif|heic|heif|webp|avif"
|
||||||
refresh: true
|
refresh: true
|
||||||
type: list
|
type: list
|
||||||
list_type: compact
|
list_type: file_types
|
||||||
authorized_extensions_for_staff:
|
authorized_extensions_for_staff:
|
||||||
client: true
|
client: true
|
||||||
default: ""
|
default: ""
|
||||||
refresh: true
|
refresh: true
|
||||||
type: list
|
type: list
|
||||||
list_type: compact
|
list_type: file_types
|
||||||
export_authorized_extensions:
|
export_authorized_extensions:
|
||||||
hidden: true
|
hidden: true
|
||||||
default: "zip"
|
default: "zip"
|
||||||
|
|
Loading…
Reference in New Issue