mirror of
https://github.com/discourse/discourse.git
synced 2025-02-26 02:16:23 +08:00

`discourse-common` was created in the past to share logic between the 'wizard' app and the main 'discourse' app. Since then, the wizard has been consolidated into the main app, so the separation of `discourse-common` is no longer useful. This commit moves `discourse-common/(lib|utils)/*` into `discourse/lib/*`, adds shims for the imports, and updates existing uses in core.
327 lines
8.1 KiB
JavaScript
327 lines
8.1 KiB
JavaScript
import { get } from "@ember/object";
|
|
import { gt, or } from "@ember/object/computed";
|
|
import { isBlank, isEmpty } from "@ember/utils";
|
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
import discourseComputed from "discourse/lib/decorators";
|
|
import RestModel from "discourse/models/rest";
|
|
import { i18n } from "discourse-i18n";
|
|
import ThemeSettings from "admin/models/theme-settings";
|
|
|
|
const THEME_UPLOAD_VAR = 2;
|
|
const FIELDS_IDS = [0, 1, 5];
|
|
|
|
export const THEMES = "themes";
|
|
export const COMPONENTS = "components";
|
|
const SETTINGS_TYPE_ID = 5;
|
|
|
|
class Theme extends RestModel {
|
|
static munge(json) {
|
|
if (json.settings) {
|
|
json.settings = json.settings.map((setting) =>
|
|
ThemeSettings.create(setting)
|
|
);
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
@or("default", "user_selectable") isActive;
|
|
@gt("remote_theme.commits_behind", 0) isPendingUpdates;
|
|
@gt("editedFields.length", 0) hasEditedFields;
|
|
@gt("parent_themes.length", 0) hasParents;
|
|
|
|
changed = false;
|
|
|
|
@discourseComputed("theme_fields.[]")
|
|
targets() {
|
|
return [
|
|
{ id: 0, name: "common" },
|
|
{ id: 1, name: "desktop", icon: "desktop" },
|
|
{ id: 2, name: "mobile", icon: "mobile-screen-button" },
|
|
{ id: 3, name: "settings", icon: "gear", advanced: true },
|
|
{
|
|
id: 4,
|
|
name: "translations",
|
|
icon: "globe",
|
|
advanced: true,
|
|
customNames: true,
|
|
},
|
|
{
|
|
id: 5,
|
|
name: "extra_scss",
|
|
icon: "paintbrush",
|
|
advanced: true,
|
|
customNames: true,
|
|
},
|
|
].map((target) => {
|
|
target["edited"] = this.hasEdited(target.name);
|
|
target["error"] = this.hasError(target.name);
|
|
return target;
|
|
});
|
|
}
|
|
|
|
@discourseComputed("theme_fields.[]")
|
|
fieldNames() {
|
|
const common = [
|
|
"scss",
|
|
"head_tag",
|
|
"header",
|
|
"after_header",
|
|
"body_tag",
|
|
"footer",
|
|
];
|
|
|
|
const scss_fields = (this.theme_fields || [])
|
|
.filter((f) => f.target === "extra_scss" && f.name !== "")
|
|
.map((f) => f.name);
|
|
|
|
if (scss_fields.length < 1) {
|
|
scss_fields.push("importable_scss");
|
|
}
|
|
|
|
return {
|
|
common: [
|
|
...common,
|
|
"color_definitions",
|
|
"embedded_scss",
|
|
"embedded_header",
|
|
],
|
|
desktop: common,
|
|
mobile: common,
|
|
settings: ["yaml"],
|
|
translations: [
|
|
"en",
|
|
...(this.theme_fields || [])
|
|
.filter((f) => f.target === "translations" && f.name !== "en")
|
|
.map((f) => f.name),
|
|
],
|
|
extra_scss: scss_fields,
|
|
};
|
|
}
|
|
|
|
@discourseComputed(
|
|
"fieldNames",
|
|
"theme_fields.[]",
|
|
"theme_fields.@each.error"
|
|
)
|
|
fields(fieldNames) {
|
|
const hash = {};
|
|
Object.keys(fieldNames).forEach((target) => {
|
|
hash[target] = fieldNames[target].map((fieldName) => {
|
|
const field = {
|
|
name: fieldName,
|
|
edited: this.hasEdited(target, fieldName),
|
|
error: this.hasError(target, fieldName),
|
|
};
|
|
|
|
if (target === "translations" || target === "extra_scss") {
|
|
field.translatedName = fieldName;
|
|
} else {
|
|
field.translatedName = i18n(
|
|
`admin.customize.theme.${fieldName}.text`
|
|
);
|
|
field.title = i18n(`admin.customize.theme.${fieldName}.title`);
|
|
}
|
|
|
|
if (fieldName.indexOf("_tag") > 0) {
|
|
field.icon = "far-file-lines";
|
|
}
|
|
|
|
return field;
|
|
});
|
|
});
|
|
return hash;
|
|
}
|
|
|
|
@discourseComputed("theme_fields")
|
|
themeFields(fields) {
|
|
if (!fields) {
|
|
this.set("theme_fields", []);
|
|
return {};
|
|
}
|
|
|
|
let hash = {};
|
|
fields.forEach((field) => {
|
|
if (!field.type_id || FIELDS_IDS.includes(field.type_id)) {
|
|
hash[this.getKey(field)] = field;
|
|
}
|
|
});
|
|
return hash;
|
|
}
|
|
|
|
@discourseComputed("theme_fields", "theme_fields.[]")
|
|
uploads(fields) {
|
|
if (!fields) {
|
|
return [];
|
|
}
|
|
return fields.filter(
|
|
(f) => f.target === "common" && f.type_id === THEME_UPLOAD_VAR
|
|
);
|
|
}
|
|
|
|
@discourseComputed("theme_fields", "theme_fields.@each.error")
|
|
isBroken(fields) {
|
|
return (
|
|
fields && fields.any((field) => field.error && field.error.length > 0)
|
|
);
|
|
}
|
|
|
|
@discourseComputed("theme_fields.[]")
|
|
editedFields(fields) {
|
|
return fields.filter(
|
|
(field) => !isBlank(field.value) && field.type_id !== SETTINGS_TYPE_ID
|
|
);
|
|
}
|
|
|
|
@discourseComputed("remote_theme.last_error_text")
|
|
remoteError(errorText) {
|
|
if (errorText && errorText.length > 0) {
|
|
return errorText;
|
|
}
|
|
}
|
|
|
|
getKey(field) {
|
|
return `${field.target} ${field.name}`;
|
|
}
|
|
|
|
hasEdited(target, name) {
|
|
if (name) {
|
|
return !isEmpty(this.getField(target, name));
|
|
} else {
|
|
let fields = this.theme_fields || [];
|
|
return fields.any(
|
|
(field) => field.target === target && !isEmpty(field.value)
|
|
);
|
|
}
|
|
}
|
|
|
|
hasError(target, name) {
|
|
return this.theme_fields
|
|
.filter((f) => f.target === target && (!name || name === f.name))
|
|
.any((f) => f.error);
|
|
}
|
|
|
|
getError(target, name) {
|
|
let themeFields = this.themeFields;
|
|
let key = this.getKey({ target, name });
|
|
let field = themeFields[key];
|
|
return field ? field.error : "";
|
|
}
|
|
|
|
getField(target, name) {
|
|
let themeFields = this.themeFields;
|
|
let key = this.getKey({ target, name });
|
|
let field = themeFields[key];
|
|
return field ? field.value : "";
|
|
}
|
|
|
|
removeField(field) {
|
|
this.set("changed", true);
|
|
|
|
field.upload_id = null;
|
|
field.value = null;
|
|
|
|
return this.saveChanges("theme_fields");
|
|
}
|
|
|
|
setField(target, name, value, upload_id, type_id) {
|
|
this.set("changed", true);
|
|
let themeFields = this.themeFields;
|
|
let field = { name, target, value, upload_id, type_id };
|
|
|
|
// slow path for uploads and so on
|
|
if (type_id && type_id > 1) {
|
|
let fields = this.theme_fields;
|
|
let existing = fields.find(
|
|
(f) => f.target === target && f.name === name && f.type_id === type_id
|
|
);
|
|
if (existing) {
|
|
existing.value = value;
|
|
existing.upload_id = upload_id;
|
|
} else {
|
|
fields.pushObject(field);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// fast path
|
|
let key = this.getKey({ target, name });
|
|
let existingField = themeFields[key];
|
|
if (!existingField) {
|
|
this.theme_fields.pushObject(field);
|
|
themeFields[key] = field;
|
|
} else {
|
|
const changed =
|
|
(isEmpty(existingField.value) && !isEmpty(value)) ||
|
|
(isEmpty(value) && !isEmpty(existingField.value));
|
|
|
|
existingField.value = value;
|
|
if (changed) {
|
|
// Observing theme_fields.@each.value is too slow, so manually notify
|
|
// if the value goes to/from blank
|
|
this.notifyPropertyChange("theme_fields.[]");
|
|
}
|
|
}
|
|
}
|
|
|
|
@discourseComputed("childThemes.[]")
|
|
child_theme_ids(childThemes) {
|
|
if (childThemes) {
|
|
return childThemes.map((theme) => get(theme, "id"));
|
|
}
|
|
}
|
|
|
|
@discourseComputed("recentlyInstalled", "component", "hasParents")
|
|
warnUnassignedComponent(recent, component, hasParents) {
|
|
return recent && component && !hasParents;
|
|
}
|
|
|
|
removeChildTheme(theme) {
|
|
const childThemes = this.childThemes;
|
|
childThemes.removeObject(theme);
|
|
return this.saveChanges("child_theme_ids");
|
|
}
|
|
|
|
addChildTheme(theme) {
|
|
let childThemes = this.childThemes;
|
|
if (!childThemes) {
|
|
childThemes = [];
|
|
this.set("childThemes", childThemes);
|
|
}
|
|
childThemes.removeObject(theme);
|
|
childThemes.pushObject(theme);
|
|
return this.saveChanges("child_theme_ids");
|
|
}
|
|
|
|
addParentTheme(theme) {
|
|
let parentThemes = this.parentThemes;
|
|
if (!parentThemes) {
|
|
parentThemes = [];
|
|
this.set("parentThemes", parentThemes);
|
|
}
|
|
parentThemes.addObject(theme);
|
|
}
|
|
|
|
checkForUpdates() {
|
|
return this.save({ remote_check: true }).then(() =>
|
|
this.set("changed", false)
|
|
);
|
|
}
|
|
|
|
updateToLatest() {
|
|
return this.save({ remote_update: true }).then(() =>
|
|
this.set("changed", false)
|
|
);
|
|
}
|
|
|
|
saveChanges() {
|
|
const hash = this.getProperties.apply(this, arguments);
|
|
return this.save(hash)
|
|
.then(() => true)
|
|
.finally(() => this.set("changed", false))
|
|
.catch(popupAjaxError);
|
|
}
|
|
}
|
|
|
|
export default Theme;
|