mirror of
https://github.com/discourse/discourse.git
synced 2025-06-24 22:11:34 +08:00
FEATURE: porting type object to site settings (#32706)
This commit is contained in:
@ -8,10 +8,10 @@ import DButton from "discourse/components/d-button";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { cloneJSON } from "discourse/lib/object";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import Tree from "admin/components/schema-theme-setting/editor/tree";
|
||||
import FieldInput from "admin/components/schema-theme-setting/field";
|
||||
import Tree from "admin/components/schema-setting/editor/tree";
|
||||
import FieldInput from "admin/components/schema-setting/field";
|
||||
|
||||
export default class SchemaThemeSettingNewEditor extends Component {
|
||||
export default class SchemaSettingNewEditor extends Component {
|
||||
@service router;
|
||||
|
||||
@tracked history = [];
|
||||
@ -21,9 +21,8 @@ export default class SchemaThemeSettingNewEditor extends Component {
|
||||
@tracked saveButtonDisabled = false;
|
||||
@tracked validationErrorMessage;
|
||||
inputFieldObserver = new Map();
|
||||
|
||||
data = cloneJSON(this.args.setting.value);
|
||||
schema = this.args.setting.objects_schema;
|
||||
schema = this.args.schema;
|
||||
|
||||
@action
|
||||
onChildClick(index, propertyName, parentNodeIndex) {
|
||||
@ -68,7 +67,7 @@ export default class SchemaThemeSettingNewEditor extends Component {
|
||||
|
||||
const lastHistory = this.history[this.history.length - 1];
|
||||
|
||||
return i18n("admin.customize.theme.schema.back_button", {
|
||||
return i18n("admin.customize.schema.back_button", {
|
||||
name: this.generateSchemaTitle(
|
||||
this.#resolveDataFromPaths(lastHistory.dataPaths)[lastHistory.index],
|
||||
this.#resolveSchemaFromPaths(lastHistory.schemaPaths),
|
||||
@ -229,16 +228,11 @@ export default class SchemaThemeSettingNewEditor extends Component {
|
||||
@action
|
||||
saveChanges() {
|
||||
this.saveButtonDisabled = true;
|
||||
|
||||
this.args.setting
|
||||
.updateSetting(this.args.themeId, this.data)
|
||||
.updateSetting(this.args.id, this.data)
|
||||
.then((result) => {
|
||||
this.args.setting.set("value", result[this.args.setting.setting]);
|
||||
|
||||
this.router.transitionTo(
|
||||
"adminCustomizeThemes.show",
|
||||
this.args.themeId
|
||||
);
|
||||
this.router.transitionTo(this.args.routeToRedirect, this.args.id);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
|
||||
@ -251,17 +245,17 @@ export default class SchemaThemeSettingNewEditor extends Component {
|
||||
}
|
||||
|
||||
<template>
|
||||
<div class="schema-theme-setting-editor">
|
||||
<div class="schema-setting-editor">
|
||||
{{#if this.validationErrorMessage}}
|
||||
<div class="schema-theme-setting-editor__errors">
|
||||
<div class="schema-setting-editor__errors">
|
||||
<div class="alert alert-error">
|
||||
{{this.validationErrorMessage}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="schema-theme-setting-editor__wrapper">
|
||||
<div class="schema-theme-setting-editor__navigation">
|
||||
<div class="schema-setting-editor__wrapper">
|
||||
<div class="schema-setting-editor__navigation">
|
||||
<Tree
|
||||
@data={{this.activeData}}
|
||||
@schema={{this.activeSchema}}
|
||||
@ -276,7 +270,7 @@ export default class SchemaThemeSettingNewEditor extends Component {
|
||||
@registerInputFieldObserver={{this.registerInputFieldObserver}}
|
||||
/>
|
||||
|
||||
<div class="schema-theme-setting-editor__footer">
|
||||
<div class="schema-setting-editor__footer">
|
||||
<DButton
|
||||
@disabled={{this.saveButtonDisabled}}
|
||||
@action={{this.saveChanges}}
|
||||
@ -286,7 +280,7 @@ export default class SchemaThemeSettingNewEditor extends Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="schema-theme-setting-editor__fields">
|
||||
<div class="schema-setting-editor__fields">
|
||||
{{#each this.fields as |field|}}
|
||||
<FieldInput
|
||||
@name={{field.name}}
|
||||
@ -303,7 +297,7 @@ export default class SchemaThemeSettingNewEditor extends Component {
|
||||
<DButton
|
||||
@action={{this.removeItem}}
|
||||
@icon="trash-can"
|
||||
class="btn-danger schema-theme-setting-editor__remove-btn"
|
||||
class="btn-danger schema-setting-editor__remove-btn"
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
@ -4,11 +4,11 @@ import icon from "discourse/helpers/d-icon";
|
||||
<template>
|
||||
<li
|
||||
role="link"
|
||||
class="schema-theme-setting-editor__tree-node --child"
|
||||
class="schema-setting-editor__tree-node --child"
|
||||
...attributes
|
||||
{{on "click" @onChildClick}}
|
||||
>
|
||||
<div class="schema-theme-setting-editor__tree-node-text">
|
||||
<div class="schema-setting-editor__tree-node-text">
|
||||
<span>{{@generateSchemaTitle @object @schema @index}}</span>
|
||||
{{icon "chevron-right"}}
|
||||
</div>
|
@ -5,9 +5,9 @@ import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import ChildTreeNode from "admin/components/schema-theme-setting/editor/child-tree-node";
|
||||
import ChildTreeNode from "admin/components/schema-setting/editor/child-tree-node";
|
||||
|
||||
export default class SchemaThemeSettingNewEditorChildTree extends Component {
|
||||
export default class SchemaSettingNewEditorChildTree extends Component {
|
||||
@tracked expanded = true;
|
||||
|
||||
@action
|
||||
@ -27,7 +27,7 @@ export default class SchemaThemeSettingNewEditorChildTree extends Component {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="schema-theme-setting-editor__tree-node --heading"
|
||||
class="schema-setting-editor__tree-node --heading"
|
||||
role="button"
|
||||
{{on "click" this.toggleVisibility}}
|
||||
>
|
||||
@ -48,12 +48,12 @@ export default class SchemaThemeSettingNewEditorChildTree extends Component {
|
||||
/>
|
||||
{{/each}}
|
||||
|
||||
<li class="schema-theme-setting-editor__tree-node --child --add-button">
|
||||
<li class="schema-setting-editor__tree-node --child --add-button">
|
||||
<DButton
|
||||
@action={{fn @addChildItem @name @parentNodeIndex}}
|
||||
@translatedLabel={{@schema.name}}
|
||||
@icon="plus"
|
||||
class="btn-transparent schema-theme-setting-editor__tree-add-button --child"
|
||||
class="btn-transparent schema-setting-editor__tree-add-button --child"
|
||||
data-test-parent-index={{@parentNodeIndex}}
|
||||
/>
|
||||
</li>
|
@ -6,9 +6,9 @@ import { gt } from "truth-helpers";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import { bind } from "discourse/lib/decorators";
|
||||
import ChildTree from "admin/components/schema-theme-setting/editor/child-tree";
|
||||
import ChildTree from "admin/components/schema-setting/editor/child-tree";
|
||||
|
||||
export default class SchemaThemeSettingNewEditorTreeNode extends Component {
|
||||
export default class SchemaSettingNewEditorTreeNode extends Component {
|
||||
@tracked text;
|
||||
|
||||
childObjectsProperties = this.findChildObjectsProperties(
|
||||
@ -52,11 +52,11 @@ export default class SchemaThemeSettingNewEditorTreeNode extends Component {
|
||||
{{on "click" @onClick}}
|
||||
role="link"
|
||||
class={{concatClass
|
||||
"schema-theme-setting-editor__tree-node --parent"
|
||||
"schema-setting-editor__tree-node --parent"
|
||||
(if @active "--active")
|
||||
}}
|
||||
>
|
||||
<div class="schema-theme-setting-editor__tree-node-text">
|
||||
<div class="schema-setting-editor__tree-node-text">
|
||||
<span>{{this.text}}</span>
|
||||
|
||||
{{#if (gt this.childObjectsProperties.length 0)}}
|
@ -3,17 +3,17 @@ import { on } from "@ember/modifier";
|
||||
import { eq } from "truth-helpers";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import TreeNode from "admin/components/schema-theme-setting/editor/tree-node";
|
||||
import TreeNode from "admin/components/schema-setting/editor/tree-node";
|
||||
|
||||
<template>
|
||||
<ul class="schema-theme-setting-editor__tree">
|
||||
<ul class="schema-setting-editor__tree">
|
||||
{{#if @backButtonText}}
|
||||
<li
|
||||
role="link"
|
||||
class="schema-theme-setting-editor__tree-node --back-btn"
|
||||
class="schema-setting-editor__tree-node --back-btn"
|
||||
{{on "click" @clickBack}}
|
||||
>
|
||||
<div class="schema-theme-setting-editor__tree-node-text">
|
||||
<div class="schema-setting-editor__tree-node-text">
|
||||
{{icon "arrow-left"}}
|
||||
{{@backButtonText}}
|
||||
</div>
|
||||
@ -34,12 +34,12 @@ import TreeNode from "admin/components/schema-theme-setting/editor/tree-node";
|
||||
/>
|
||||
{{/each}}
|
||||
|
||||
<li class="schema-theme-setting-editor__tree-node --parent --add-button">
|
||||
<li class="schema-setting-editor__tree-node --parent --add-button">
|
||||
<DButton
|
||||
@action={{@addItem}}
|
||||
@translatedLabel={{@schema.name}}
|
||||
@icon="plus"
|
||||
class="btn-transparent schema-theme-setting-editor__tree-add-button --root"
|
||||
class="btn-transparent schema-setting-editor__tree-add-button --root"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
@ -1,16 +1,16 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { cached } from "@glimmer/tracking";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import BooleanField from "admin/components/schema-theme-setting/types/boolean";
|
||||
import CategoriesField from "admin/components/schema-theme-setting/types/categories";
|
||||
import EnumField from "admin/components/schema-theme-setting/types/enum";
|
||||
import FloatField from "admin/components/schema-theme-setting/types/float";
|
||||
import GroupsField from "admin/components/schema-theme-setting/types/groups";
|
||||
import IntegerField from "admin/components/schema-theme-setting/types/integer";
|
||||
import StringField from "admin/components/schema-theme-setting/types/string";
|
||||
import TagsField from "admin/components/schema-theme-setting/types/tags";
|
||||
import BooleanField from "admin/components/schema-setting/types/boolean";
|
||||
import CategoriesField from "admin/components/schema-setting/types/categories";
|
||||
import EnumField from "admin/components/schema-setting/types/enum";
|
||||
import FloatField from "admin/components/schema-setting/types/float";
|
||||
import GroupsField from "admin/components/schema-setting/types/groups";
|
||||
import IntegerField from "admin/components/schema-setting/types/integer";
|
||||
import StringField from "admin/components/schema-setting/types/string";
|
||||
import TagsField from "admin/components/schema-setting/types/tags";
|
||||
|
||||
export default class SchemaThemeSettingField extends Component {
|
||||
export default class SchemaSettingField extends Component {
|
||||
get component() {
|
||||
const type = this.args.spec.type;
|
||||
|
@ -5,9 +5,9 @@ import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { and, not } from "truth-helpers";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import FieldInputDescription from "admin/components/schema-theme-setting/field-input-description";
|
||||
import FieldInputDescription from "admin/components/schema-setting/field-input-description";
|
||||
|
||||
export default class SchemaThemeSettingNumberField extends Component {
|
||||
export default class SchemaSettingNumberField extends Component {
|
||||
@tracked touched = false;
|
||||
@tracked value = this.args.value;
|
||||
min = this.args.spec.validations?.min;
|
||||
@ -43,20 +43,20 @@ export default class SchemaThemeSettingNumberField extends Component {
|
||||
|
||||
if (!this.value) {
|
||||
if (this.required) {
|
||||
return i18n("admin.customize.theme.schema.fields.required");
|
||||
return i18n("admin.customize.schema.fields.required");
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.min && this.value < this.min) {
|
||||
return i18n("admin.customize.theme.schema.fields.number.too_small", {
|
||||
return i18n("admin.customize.schema.fields.number.too_small", {
|
||||
count: this.min,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.max && this.value > this.max) {
|
||||
return i18n("admin.customize.theme.schema.fields.number.too_large", {
|
||||
return i18n("admin.customize.schema.fields.number.too_large", {
|
||||
count: this.max,
|
||||
});
|
||||
}
|
@ -2,9 +2,9 @@ import Component from "@glimmer/component";
|
||||
import { Input } from "@ember/component";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import FieldInputDescription from "admin/components/schema-theme-setting/field-input-description";
|
||||
import FieldInputDescription from "admin/components/schema-setting/field-input-description";
|
||||
|
||||
export default class SchemaThemeSettingTypeBoolean extends Component {
|
||||
export default class SchemaSettingTypeBoolean extends Component {
|
||||
@action
|
||||
onInput(event) {
|
||||
this.args.onChange(event.currentTarget.checked);
|
@ -1,11 +1,11 @@
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { hash } from "@ember/helper";
|
||||
import { and, not } from "truth-helpers";
|
||||
import FieldInputDescription from "admin/components/schema-theme-setting/field-input-description";
|
||||
import SchemaThemeSettingTypeModels from "admin/components/schema-theme-setting/types/models";
|
||||
import FieldInputDescription from "admin/components/schema-setting/field-input-description";
|
||||
import SchemaSettingTypeModels from "admin/components/schema-setting/types/models";
|
||||
import CategorySelector from "select-kit/components/category-selector";
|
||||
|
||||
export default class SchemaThemeSettingTypeCategories extends SchemaThemeSettingTypeModels {
|
||||
export default class SchemaSettingTypeCategories extends SchemaSettingTypeModels {
|
||||
@tracked
|
||||
value =
|
||||
this.args.value?.map((categoryId) => {
|
@ -1,10 +1,10 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import FieldInputDescription from "admin/components/schema-theme-setting/field-input-description";
|
||||
import FieldInputDescription from "admin/components/schema-setting/field-input-description";
|
||||
import ComboBox from "select-kit/components/combo-box";
|
||||
|
||||
export default class SchemaThemeSettingTypeEnum extends Component {
|
||||
export default class SchemaSettingTypeEnum extends Component {
|
||||
@tracked
|
||||
value =
|
||||
this.args.value || (this.args.spec.required && this.args.spec.default);
|
@ -0,0 +1,9 @@
|
||||
import SchemaSettingNumberField from "admin/components/schema-setting/number-field";
|
||||
|
||||
export default class SchemaSettingTypeFloat extends SchemaSettingNumberField {
|
||||
step = 0.1;
|
||||
|
||||
parseValue(value) {
|
||||
return parseFloat(value);
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import { service } from "@ember/service";
|
||||
import { and, not } from "truth-helpers";
|
||||
import FieldInputDescription from "admin/components/schema-theme-setting/field-input-description";
|
||||
import SchemaThemeSettingTypeModels from "admin/components/schema-theme-setting/types/models";
|
||||
import FieldInputDescription from "admin/components/schema-setting/field-input-description";
|
||||
import SchemaSettingTypeModels from "admin/components/schema-setting/types/models";
|
||||
import GroupChooser from "select-kit/components/group-chooser";
|
||||
|
||||
export default class SchemaThemeSettingTypeGroups extends SchemaThemeSettingTypeModels {
|
||||
export default class SchemaSettingTypeGroups extends SchemaSettingTypeModels {
|
||||
@service site;
|
||||
|
||||
type = "groups";
|
@ -0,0 +1,10 @@
|
||||
import SchemaSettingNumberField from "admin/components/schema-setting/number-field";
|
||||
|
||||
export default class SchemaSettingTypeInteger extends SchemaSettingNumberField {
|
||||
inputMode = "numeric";
|
||||
pattern = "[0-9]*";
|
||||
|
||||
parseValue(value) {
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import { action } from "@ember/object";
|
||||
import { isBlank } from "@ember/utils";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class SchemaThemeSettingTypeModels extends Component {
|
||||
export default class SchemaSettingTypeModels extends Component {
|
||||
@tracked value = this.args.value;
|
||||
|
||||
required = this.args.spec.required;
|
||||
@ -33,7 +33,7 @@ export default class SchemaThemeSettingTypeModels extends Component {
|
||||
(this.min && this.value && this.value.length < this.min) ||
|
||||
(this.required && isValueBlank)
|
||||
) {
|
||||
return i18n(`admin.customize.theme.schema.fields.${this.type}.at_least`, {
|
||||
return i18n(`admin.customize.schema.fields.${this.type}.at_least`, {
|
||||
count: this.min || 1,
|
||||
});
|
||||
}
|
@ -6,9 +6,9 @@ import { action } from "@ember/object";
|
||||
import { and, not } from "truth-helpers";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import FieldInputDescription from "admin/components/schema-theme-setting/field-input-description";
|
||||
import FieldInputDescription from "admin/components/schema-setting/field-input-description";
|
||||
|
||||
export default class SchemaThemeSettingTypeString extends Component {
|
||||
export default class SchemaSettingTypeString extends Component {
|
||||
@tracked touched = false;
|
||||
@tracked value = this.args.value || "";
|
||||
minLength = this.args.spec.validations?.min_length;
|
||||
@ -32,14 +32,14 @@ export default class SchemaThemeSettingTypeString extends Component {
|
||||
|
||||
if (valueLength === 0) {
|
||||
if (this.required) {
|
||||
return i18n("admin.customize.theme.schema.fields.required");
|
||||
return i18n("admin.customize.schema.fields.required");
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.minLength && valueLength < this.minLength) {
|
||||
return i18n("admin.customize.theme.schema.fields.string.too_short", {
|
||||
return i18n("admin.customize.schema.fields.string.too_short", {
|
||||
count: this.minLength,
|
||||
});
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { and, not } from "truth-helpers";
|
||||
import FieldInputDescription from "admin/components/schema-theme-setting/field-input-description";
|
||||
import SchemaThemeSettingTypeModels from "admin/components/schema-theme-setting/types/models";
|
||||
import FieldInputDescription from "admin/components/schema-setting/field-input-description";
|
||||
import SchemaSettingTypeModels from "admin/components/schema-setting/types/models";
|
||||
import TagChooser from "select-kit/components/tag-chooser";
|
||||
|
||||
export default class SchemaThemeSettingTypeTags extends SchemaThemeSettingTypeModels {
|
||||
export default class SchemaSettingTypeTags extends SchemaSettingTypeModels {
|
||||
type = "tags";
|
||||
|
||||
get tagChooserOption() {
|
@ -1,9 +0,0 @@
|
||||
import SchemaThemeSettingNumberField from "admin/components/schema-theme-setting/number-field";
|
||||
|
||||
export default class SchemaThemeSettingTypeFloat extends SchemaThemeSettingNumberField {
|
||||
step = 0.1;
|
||||
|
||||
parseValue(value) {
|
||||
return parseFloat(value);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import SchemaThemeSettingNumberField from "admin/components/schema-theme-setting/number-field";
|
||||
|
||||
export default class SchemaThemeSettingTypeInteger extends SchemaThemeSettingNumberField {
|
||||
inputMode = "numeric";
|
||||
pattern = "[0-9]*";
|
||||
|
||||
parseValue(value) {
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
}
|
@ -165,6 +165,14 @@ export default Mixin.create({
|
||||
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: () => {
|
||||
|
@ -31,7 +31,6 @@ export default class SiteSetting extends EmberObject {
|
||||
}
|
||||
categories[s.category].pushObject(SiteSetting.create(s));
|
||||
});
|
||||
|
||||
return Object.keys(categories).map(function (n) {
|
||||
return {
|
||||
nameKey: n,
|
||||
@ -43,6 +42,17 @@ export default class SiteSetting extends EmberObject {
|
||||
);
|
||||
}
|
||||
|
||||
static findByName(name) {
|
||||
return ajax("/admin/site_settings", {
|
||||
data: {
|
||||
names: [name],
|
||||
},
|
||||
}).then(function (settings) {
|
||||
const setting = settings.site_settings.find((s) => s.setting === name);
|
||||
return SiteSetting.create(setting);
|
||||
});
|
||||
}
|
||||
|
||||
static update(key, value, opts = {}) {
|
||||
const data = {};
|
||||
data[key] = value;
|
||||
|
@ -392,7 +392,7 @@ export default function () {
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
this.route("schema", { path: "schema/:setting_name" });
|
||||
this.route(
|
||||
"adminPlugins",
|
||||
{ path: "/plugins", resetNamespace: true },
|
||||
|
32
app/assets/javascripts/admin/addon/routes/admin-schema.js
Normal file
32
app/assets/javascripts/admin/addon/routes/admin-schema.js
Normal file
@ -0,0 +1,32 @@
|
||||
import Route from "@ember/routing/route";
|
||||
import { service } from "@ember/service";
|
||||
import SiteSetting from "admin/models/site-setting";
|
||||
|
||||
export default class AdminSchemaRoute extends Route {
|
||||
@service routeHistory;
|
||||
|
||||
async model(params) {
|
||||
const setting = await SiteSetting.findByName(params.setting_name);
|
||||
|
||||
try {
|
||||
setting.value = JSON.parse(setting.value);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`Failed to parse plugin setting ${setting.setting} value: ${setting.value}`,
|
||||
e
|
||||
);
|
||||
setting.value = {};
|
||||
}
|
||||
|
||||
setting.updateSetting = (settingName, value) => {
|
||||
return SiteSetting.update(settingName, JSON.stringify(value));
|
||||
};
|
||||
|
||||
return {
|
||||
setting,
|
||||
settingName: params.setting_name,
|
||||
goBackUrl: this.routeHistory.lastURL,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { hash } from "@ember/helper";
|
||||
import RouteTemplate from "ember-route-template";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import Editor from "admin/components/schema-setting/editor";
|
||||
|
||||
export default RouteTemplate(
|
||||
<template>
|
||||
<div class="customize-show-schema__header row">
|
||||
<a href={{@model.goBackUrl}}>
|
||||
{{icon "arrow-left"}}
|
||||
</a>
|
||||
<h2>
|
||||
{{i18n "admin.customize.schema.title" (hash name=@model.settingName)}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Editor
|
||||
@id={{@model.settingName}}
|
||||
@routeToRedirect={{@model.goBackUrl}}
|
||||
@schema={{@model.setting.schema}}
|
||||
@setting={{@model.setting}}
|
||||
/>
|
||||
</template>
|
||||
);
|
@ -3,26 +3,31 @@ import { LinkTo } from "@ember/routing";
|
||||
import RouteTemplate from "ember-route-template";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import Editor from "admin/components/schema-theme-setting/editor";
|
||||
import Editor from "admin/components/schema-setting/editor";
|
||||
|
||||
export default RouteTemplate(
|
||||
<template>
|
||||
<div class="customize-themes-show-schema__header row">
|
||||
<div class="customize-show-schema__header row">
|
||||
<LinkTo
|
||||
@route="adminCustomizeThemes.show"
|
||||
@model={{@model.theme.id}}
|
||||
class="btn-transparent customize-themes-show-schema__back"
|
||||
class="btn-transparent customize-show-schema__back"
|
||||
>
|
||||
{{icon "arrow-left"}}{{@model.theme.name}}
|
||||
</LinkTo>
|
||||
<h2>
|
||||
{{i18n
|
||||
"admin.customize.theme.schema.title"
|
||||
"admin.customize.schema.title"
|
||||
(hash name=@model.setting.setting)
|
||||
}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Editor @themeId={{@model.theme.id}} @setting={{@model.setting}} />
|
||||
<Editor
|
||||
@id={{@model.theme.id}}
|
||||
@routeToRedirect="adminCustomizeThemes.show"
|
||||
@schema={{@model.setting.objects_schema}}
|
||||
@setting={{@model.setting}}
|
||||
/>
|
||||
</template>
|
||||
);
|
||||
|
@ -1,6 +1,10 @@
|
||||
import ThemeSettings from "admin/models/theme-settings";
|
||||
|
||||
export default function schemaAndData(version = 1) {
|
||||
import SiteSetting from "admin/models/site-setting";
|
||||
export const SCHEMA_MODES = {
|
||||
THEME: "theme",
|
||||
SITE_SETTING: "SITE_SETTING",
|
||||
};
|
||||
export default function schemaAndData(version = 1, mode = SCHEMA_MODES.THEME) {
|
||||
let schema, data;
|
||||
|
||||
if (version === 1) {
|
||||
@ -206,6 +210,14 @@ export default function schemaAndData(version = 1) {
|
||||
throw new Error("unknown fixture version");
|
||||
}
|
||||
|
||||
if (mode === SCHEMA_MODES.SITE_SETTING) {
|
||||
return SiteSetting.create({
|
||||
schema: schema,
|
||||
value: data,
|
||||
setting: "objects_setting"
|
||||
})
|
||||
}
|
||||
|
||||
return ThemeSettings.create({
|
||||
objects_schema: schema,
|
||||
value: data,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1275,8 +1275,7 @@ a.inline-editable-field {
|
||||
@import "admin/admin_section_landing_page";
|
||||
@import "admin/admin_intro";
|
||||
@import "admin/mini_profiler";
|
||||
@import "admin/schema_theme_setting_editor";
|
||||
@import "admin/customize_themes_show_schema";
|
||||
@import "admin/schema_setting_editor";
|
||||
@import "admin/admin_bulk_users_delete_modal";
|
||||
@import "admin/color-palette-editor";
|
||||
@import "admin/admin_config_color_palettes";
|
||||
|
@ -1,4 +1,4 @@
|
||||
.customize-themes-show-schema {
|
||||
.customize-show-schema {
|
||||
&__header {
|
||||
margin-bottom: 1em;
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
gap: 0 1em;
|
||||
}
|
||||
|
||||
.schema-theme-setting-editor__navigation {
|
||||
.schema-setting-editor__navigation {
|
||||
overflow: hidden;
|
||||
align-self: start;
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.schema-theme-setting-editor__tree {
|
||||
.schema-setting-editor__tree {
|
||||
border: 1px solid var(--primary-low);
|
||||
overflow: auto;
|
||||
margin: 0 0 2em 0;
|
||||
@ -52,7 +52,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.schema-theme-setting-editor__tree-node.--back-btn {
|
||||
.schema-setting-editor__tree-node.--back-btn {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--primary-low);
|
||||
@ -63,7 +63,7 @@
|
||||
background: var(--primary-very-low);
|
||||
}
|
||||
|
||||
.schema-theme-setting-editor__tree-node-text {
|
||||
.schema-setting-editor__tree-node-text {
|
||||
color: currentcolor;
|
||||
|
||||
.d-icon {
|
||||
@ -74,7 +74,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.schema-theme-setting-editor__tree-node-text {
|
||||
.schema-setting-editor__tree-node-text {
|
||||
padding: var(--schema-space);
|
||||
color: var(--primary);
|
||||
display: flex;
|
||||
@ -91,11 +91,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.schema-theme-setting-editor__tree-node {
|
||||
.schema-setting-editor__tree-node {
|
||||
cursor: pointer;
|
||||
|
||||
&.--active {
|
||||
> .schema-theme-setting-editor__tree-node-text {
|
||||
> .schema-setting-editor__tree-node-text {
|
||||
background-color: var(--tertiary);
|
||||
color: var(--secondary);
|
||||
|
||||
@ -137,14 +137,14 @@
|
||||
margin-left: var(--schema-space);
|
||||
border-left: 1px solid var(--primary-200);
|
||||
|
||||
.schema-theme-setting-editor__tree-node-text {
|
||||
.schema-setting-editor__tree-node-text {
|
||||
color: var(--primary-800);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.schema-theme-setting-editor__tree-add-button {
|
||||
.schema-setting-editor__tree-add-button {
|
||||
color: var(--tertiary);
|
||||
width: 100%;
|
||||
line-height: 1.4; // match li height
|
@ -6,13 +6,13 @@ class Admin::SiteSettingsController < Admin::AdminController
|
||||
end
|
||||
|
||||
def index
|
||||
params.permit(:categories, :plugin)
|
||||
|
||||
params.permit(:categories, :plugin, :names)
|
||||
render_json_dump(
|
||||
site_settings:
|
||||
SiteSetting.all_settings(
|
||||
filter_categories: params[:categories],
|
||||
filter_plugin: params[:plugin],
|
||||
filter_names: params[:names],
|
||||
),
|
||||
)
|
||||
end
|
||||
|
@ -6702,31 +6702,31 @@ en:
|
||||
active_filter: "Active"
|
||||
inactive_filter: "Inactive"
|
||||
updates_available_filter: "Updates Available"
|
||||
schema:
|
||||
title: "Edit %{name} setting"
|
||||
back_button: "Back to %{name}"
|
||||
fields:
|
||||
required: "*required"
|
||||
groups:
|
||||
at_least:
|
||||
one: "at least %{count} group is required"
|
||||
other: "at least %{count} groups are required"
|
||||
categories:
|
||||
at_least:
|
||||
one: "at least %{count} category is required"
|
||||
other: "at least %{count} categories are required"
|
||||
tags:
|
||||
at_least:
|
||||
one: "at least %{count} tag is required"
|
||||
other: "at least %{count} tags are required"
|
||||
string:
|
||||
too_short:
|
||||
one: "must be at least %{count} character"
|
||||
other: "must be at least %{count} characters"
|
||||
number:
|
||||
too_small: "must be greater than or equal to %{count}"
|
||||
too_large: "must be less than or equal to %{count}"
|
||||
|
||||
schema:
|
||||
title: "Edit %{name} setting"
|
||||
back_button: "Back to %{name}"
|
||||
fields:
|
||||
required: "*required"
|
||||
groups:
|
||||
at_least:
|
||||
one: "at least %{count} group is required"
|
||||
other: "at least %{count} groups are required"
|
||||
categories:
|
||||
at_least:
|
||||
one: "at least %{count} category is required"
|
||||
other: "at least %{count} categories are required"
|
||||
tags:
|
||||
at_least:
|
||||
one: "at least %{count} tag is required"
|
||||
other: "at least %{count} tags are required"
|
||||
string:
|
||||
too_short:
|
||||
one: "must be at least %{count} character"
|
||||
other: "must be at least %{count} characters"
|
||||
number:
|
||||
too_small: "must be greater than or equal to %{count}"
|
||||
too_large: "must be less than or equal to %{count}"
|
||||
colors:
|
||||
select_base:
|
||||
title: "Select base color palette"
|
||||
|
@ -2790,6 +2790,7 @@ en:
|
||||
one: "Must be no more than %{count} character."
|
||||
other: "Must be no more than %{count} characters."
|
||||
invalid_json: "Invalid JSON."
|
||||
invalid_object: "Invalid object."
|
||||
invalid_reply_by_email_address: "Value must contain '%{reply_key}' and be different from the notification email."
|
||||
invalid_alternative_reply_by_email_addresses: "All values must contain '%{reply_key}' and be different from the notification email."
|
||||
invalid_domain_hostname: "Must not include * or ? characters."
|
||||
|
@ -102,6 +102,7 @@ Discourse::Application.routes.draw do
|
||||
namespace :admin, constraints: StaffConstraint.new do
|
||||
get "" => "admin#index"
|
||||
get "search" => "search#index"
|
||||
get "schema/:setting_name" => "admin#index"
|
||||
|
||||
get "plugins" => "plugins#index"
|
||||
get "plugins/:plugin_id" => "plugins#show"
|
||||
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ThemeSettingsObjectValidator
|
||||
class SchemaSettingsObjectValidator
|
||||
class << self
|
||||
def validate_objects(schema:, objects:)
|
||||
error_messages = []
|
||||
@ -25,13 +25,13 @@ class ThemeSettingsObjectValidator
|
||||
end
|
||||
end
|
||||
|
||||
class ThemeSettingsObjectErrors
|
||||
class SchemaSettingsObjectErrors
|
||||
def initialize
|
||||
@errors = []
|
||||
end
|
||||
|
||||
def add_error(error, i18n_opts = {})
|
||||
@errors << ThemeSettingsObjectError.new(error, i18n_opts)
|
||||
@errors << SchemaSettingsObjectError.new(error, i18n_opts)
|
||||
end
|
||||
|
||||
def humanize_messages(property_json_pointer)
|
||||
@ -42,7 +42,7 @@ class ThemeSettingsObjectValidator
|
||||
@errors.map(&:error_message)
|
||||
end
|
||||
end
|
||||
class ThemeSettingsObjectError
|
||||
class SchemaSettingsObjectError
|
||||
def initialize(error, i18n_opts = {})
|
||||
@error = error
|
||||
@i18n_opts = i18n_opts
|
||||
@ -218,7 +218,7 @@ class ThemeSettingsObjectValidator
|
||||
|
||||
def add_error(property_name, key, i18n_opts = {})
|
||||
pointer = json_pointer(property_name)
|
||||
@errors[pointer] ||= ThemeSettingsObjectErrors.new
|
||||
@errors[pointer] ||= SchemaSettingsObjectErrors.new
|
||||
@errors[pointer].add_error(key, i18n_opts)
|
||||
end
|
||||
|
@ -20,9 +20,10 @@ class SiteSettings::TypeSupervisor
|
||||
list_type
|
||||
textarea
|
||||
json_schema
|
||||
schema
|
||||
requires_confirmation
|
||||
].freeze
|
||||
VALIDATOR_OPTS = %i[min max regex hidden regex_error json_schema].freeze
|
||||
VALIDATOR_OPTS = %i[min max regex hidden regex_error json_schema schema].freeze
|
||||
|
||||
# For plugins, so they can tell if a feature is supported
|
||||
SUPPORTED_TYPES = %i[email username list enum].freeze
|
||||
@ -59,6 +60,7 @@ class SiteSettings::TypeSupervisor
|
||||
html_deprecated: 25,
|
||||
tag_group_list: 26,
|
||||
file_size_restriction: 27,
|
||||
objects: 28,
|
||||
)
|
||||
end
|
||||
|
||||
@ -94,6 +96,7 @@ class SiteSettings::TypeSupervisor
|
||||
@list_type = {}
|
||||
@textareas = {}
|
||||
@json_schemas = {}
|
||||
@schemas = {}
|
||||
end
|
||||
|
||||
def load_setting(name_arg, opts = {})
|
||||
@ -102,6 +105,7 @@ class SiteSettings::TypeSupervisor
|
||||
@textareas[name] = opts[:textarea] if opts[:textarea]
|
||||
|
||||
@json_schemas[name] = opts[:json_schema].constantize if opts[:json_schema]
|
||||
@schemas[name] = opts[:schema] if opts[:schema]
|
||||
|
||||
if (enum = opts[:enum])
|
||||
@enums[name] = enum.is_a?(String) ? enum.constantize : enum
|
||||
@ -125,6 +129,11 @@ class SiteSettings::TypeSupervisor
|
||||
@allow_any[name] = opts[:allow_any] == false ? false : true
|
||||
@list_type[name] = opts[:list_type] if opts[:list_type]
|
||||
end
|
||||
|
||||
# add validator for objects
|
||||
if type.to_sym == :objects
|
||||
@validators[name] = { class: ObjectsSettingValidator, opts: { schema: opts[:schema] } }
|
||||
end
|
||||
end
|
||||
@types[name] = get_data_type(name, @defaults_provider[name])
|
||||
|
||||
@ -140,7 +149,6 @@ class SiteSettings::TypeSupervisor
|
||||
name = name.to_sym
|
||||
@types[name] = (@types[name] || get_data_type(name, value))
|
||||
type = (override_type || @types[name])
|
||||
|
||||
case type
|
||||
when self.class.types[:float]
|
||||
value.to_f
|
||||
@ -172,7 +180,6 @@ class SiteSettings::TypeSupervisor
|
||||
def type_hash(name)
|
||||
name = name.to_sym
|
||||
type = get_type(name)
|
||||
|
||||
result = { type: type.to_s }
|
||||
|
||||
if type == :enum
|
||||
@ -202,6 +209,8 @@ class SiteSettings::TypeSupervisor
|
||||
result[:choices] = @choices[name] if @choices.has_key? name
|
||||
result[:list_type] = @list_type[name] if @list_type.has_key? name
|
||||
result[:textarea] = @textareas[name] if @textareas.has_key? name
|
||||
result[:schema] = @schemas[name] if @schemas.has_key? name
|
||||
|
||||
if @json_schemas.has_key?(name) && json_klass = json_schema_class(name)
|
||||
result[:json_schema] = json_klass.schema
|
||||
end
|
||||
|
@ -26,7 +26,7 @@ class ThemeSettingsManager::Objects < ThemeSettingsManager
|
||||
|
||||
value.each do |theme_setting_object|
|
||||
category_ids.merge(
|
||||
ThemeSettingsObjectValidator.new(
|
||||
SchemaSettingsObjectValidator.new(
|
||||
schema:,
|
||||
object: theme_setting_object,
|
||||
).property_values_of_type("categories"),
|
||||
|
@ -53,7 +53,7 @@ class ThemeSettingsValidator
|
||||
)
|
||||
when types[:objects]
|
||||
errors.concat(
|
||||
ThemeSettingsObjectValidator.validate_objects(schema: opts[:schema], objects: value),
|
||||
SchemaSettingsObjectValidator.validate_objects(schema: opts[:schema], objects: value),
|
||||
)
|
||||
end
|
||||
|
||||
|
31
lib/validators/objects_setting_validator.rb
Normal file
31
lib/validators/objects_setting_validator.rb
Normal file
@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ObjectsSettingValidator
|
||||
def initialize(opts = {})
|
||||
@opts = opts
|
||||
end
|
||||
|
||||
def valid_value?(val)
|
||||
parsed_value = val.is_a?(String) ? JSON.parse(val) : val
|
||||
if parsed_value.nil? || !parsed_value.is_a?(Array)
|
||||
@error = I18n.t("site_settings.errors.invalid_object")
|
||||
return false
|
||||
end
|
||||
errors =
|
||||
SchemaSettingsObjectValidator.validate_objects(schema: @opts[:schema], objects: parsed_value)
|
||||
if errors.empty?
|
||||
@error = nil
|
||||
true
|
||||
else
|
||||
@error = errors.map(&:full_messages).flatten.join(", ")
|
||||
false
|
||||
end
|
||||
rescue StandardError
|
||||
@error = I18n.t("site_settings.errors.invalid_object")
|
||||
false
|
||||
end
|
||||
|
||||
def error_message
|
||||
@error
|
||||
end
|
||||
end
|
@ -186,6 +186,20 @@ RSpec.describe SiteSettings::TypeSupervisor do
|
||||
"[{\"name\":\"Brett\"}]",
|
||||
json_schema: "TestJsonSchemaClass",
|
||||
)
|
||||
settings.setting(
|
||||
:type_objects,
|
||||
"[]",
|
||||
type: "objects",
|
||||
schema: {
|
||||
name: "link",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
settings.refresh!
|
||||
end
|
||||
|
||||
@ -327,6 +341,30 @@ RSpec.describe SiteSettings::TypeSupervisor do
|
||||
SiteSetting.types[:string],
|
||||
]
|
||||
end
|
||||
|
||||
it "raises when an object is not valid for the given schema" do
|
||||
expect {
|
||||
settings.type_supervisor.to_db_value(:type_objects, "not-json")
|
||||
}.to raise_error Discourse::InvalidParameters
|
||||
end
|
||||
|
||||
it "raises when an object property is not valid for the given schema" do
|
||||
expect {
|
||||
settings.type_supervisor.to_db_value(:type_objects, "[{\"nam\":\"Brett\"}]")
|
||||
}.to raise_error Discourse::InvalidParameters
|
||||
end
|
||||
|
||||
it "raises when an object value is not valid for the given schema" do
|
||||
expect {
|
||||
settings.type_supervisor.to_db_value(:type_objects, "[{\"name\":1}]")
|
||||
}.to raise_error Discourse::InvalidParameters
|
||||
end
|
||||
|
||||
it "returns value for the given objects schema string setting" do
|
||||
expect(
|
||||
settings.type_supervisor.to_db_value(:type_objects, "[{\"name\":\"Brett\"}]"),
|
||||
).to eq ["[{\"name\":\"Brett\"}]", SiteSetting.types[:objects]]
|
||||
end
|
||||
end
|
||||
|
||||
describe "#to_rb_value" do
|
||||
@ -446,6 +484,19 @@ RSpec.describe SiteSettings::TypeSupervisor do
|
||||
settings.setting(:type_enum_choices, "2", type: "enum", choices: %w[1 2])
|
||||
settings.setting(:type_enum_class, "a", enum: "TestEnumClass2")
|
||||
settings.setting(:type_list, "a", type: "list", choices: %w[a b], list_type: "compact")
|
||||
settings.setting(
|
||||
:type_objects,
|
||||
"[]",
|
||||
type: "objects",
|
||||
schema: {
|
||||
name: "link",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
settings.refresh!
|
||||
end
|
||||
|
||||
@ -501,6 +552,12 @@ RSpec.describe SiteSettings::TypeSupervisor do
|
||||
expect(hash[:translate_names]).to eq false
|
||||
end
|
||||
|
||||
it "returns objects type" do
|
||||
hash = settings.type_supervisor.type_hash(:type_objects)
|
||||
expect(hash[:type]).to eq "objects"
|
||||
expect(hash[:schema]).to eq({ name: "link", properties: { name: { type: "string" } } })
|
||||
end
|
||||
|
||||
it "returns int min/max values" do
|
||||
expect(settings.type_supervisor.type_hash(:type_int)[:min]).to eq(-10)
|
||||
expect(settings.type_supervisor.type_hash(:type_int)[:max]).to eq(10)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe ThemeSettingsObjectValidator do
|
||||
RSpec.describe SchemaSettingsObjectValidator do
|
||||
describe ".validate_objects" do
|
||||
it "should return the right array of humanized error messages for objects that are invalid" do
|
||||
schema = {
|
||||
|
@ -112,7 +112,7 @@ RSpec.describe "Admin editing objects type theme setting", type: :system do
|
||||
.fill_in_field("name", "")
|
||||
.save
|
||||
|
||||
expect(find(".schema-theme-setting-editor__errors")).to have_text(
|
||||
expect(find(".schema-setting-editor__errors")).to have_text(
|
||||
"The property at JSON Pointer '/0/name' must be present. The property at JSON Pointer '/1/name' must be present. The property at JSON Pointer '/1/links/0/name' must be present.",
|
||||
)
|
||||
end
|
||||
|
@ -22,7 +22,7 @@ module PageObjects
|
||||
|
||||
def click_link(name, child: false)
|
||||
find(
|
||||
".schema-theme-setting-editor__navigation .schema-theme-setting-editor__tree-node#{child ? ".--child" : ".--parent"}",
|
||||
".schema-setting-editor__navigation .schema-setting-editor__tree-node#{child ? ".--child" : ".--parent"}",
|
||||
text: name,
|
||||
).click
|
||||
|
||||
@ -44,7 +44,7 @@ module PageObjects
|
||||
end
|
||||
|
||||
def back
|
||||
find(".customize-themes-show-schema__back").click
|
||||
find(".customize-show-schema__back").click
|
||||
self
|
||||
end
|
||||
|
||||
|
Reference in New Issue
Block a user