Since 65e123498b, it is now impossible to make local changes to remote themes, so this warning is not needed.
315 lines
7.8 KiB
JavaScript
315 lines
7.8 KiB
JavaScript
import I18n from "I18n";
|
|
import { get } from "@ember/object";
|
|
import { isBlank, isEmpty } from "@ember/utils";
|
|
import { or, gt } from "@ember/object/computed";
|
|
import RestModel from "discourse/models/rest";
|
|
import discourseComputed from "discourse-common/utils/decorators";
|
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
|
|
const THEME_UPLOAD_VAR = 2;
|
|
const FIELDS_IDS = [0, 1, 5];
|
|
|
|
export const THEMES = "themes";
|
|
export const COMPONENTS = "components";
|
|
const SETTINGS_TYPE_ID = 5;
|
|
|
|
const Theme = RestModel.extend({
|
|
isActive: or("default", "user_selectable"),
|
|
isPendingUpdates: gt("remote_theme.commits_behind", 0),
|
|
hasEditedFields: gt("editedFields.length", 0),
|
|
hasParents: gt("parent_themes.length", 0),
|
|
|
|
@discourseComputed("theme_fields.[]")
|
|
targets() {
|
|
return [
|
|
{ id: 0, name: "common" },
|
|
{ id: 1, name: "desktop", icon: "desktop" },
|
|
{ id: 2, name: "mobile", icon: "mobile-alt" },
|
|
{ id: 3, name: "settings", icon: "cog", advanced: true },
|
|
{
|
|
id: 4,
|
|
name: "translations",
|
|
icon: "globe",
|
|
advanced: true,
|
|
customNames: true,
|
|
},
|
|
{
|
|
id: 5,
|
|
name: "extra_scss",
|
|
icon: "paint-brush",
|
|
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, "embedded_scss", "color_definitions"],
|
|
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.t(
|
|
`admin.customize.theme.${fieldName}.text`
|
|
);
|
|
field.title = I18n.t(`admin.customize.theme.${fieldName}.title`);
|
|
}
|
|
|
|
if (fieldName.indexOf("_tag") > 0) {
|
|
field.icon = "far-file-alt";
|
|
}
|
|
|
|
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"));
|
|
}
|
|
},
|
|
|
|
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)
|
|
);
|
|
},
|
|
|
|
changed: false,
|
|
|
|
saveChanges() {
|
|
const hash = this.getProperties.apply(this, arguments);
|
|
return this.save(hash)
|
|
.finally(() => this.set("changed", false))
|
|
.catch(popupAjaxError);
|
|
},
|
|
|
|
saveSettings(name, value) {
|
|
const settings = {};
|
|
settings[name] = value;
|
|
return this.save({ settings });
|
|
},
|
|
|
|
saveTranslation(name, value) {
|
|
return this.save({ translations: { [name]: value } });
|
|
},
|
|
});
|
|
|
|
export default Theme;
|