mirror of
https://github.com/discourse/discourse.git
synced 2025-02-07 03:48:23 +00:00
e029a9b36c
A public key must be added to GitHub when installing private themes. When the process happens asynchronously (for example if the admin does not have admin permissions to the GitHub repository), installing private themes becomes very difficult. In this case, the Discourse admin can partially install the theme by letting Discourse save the private key, create a placeholder theme and give the admin a public key to be used as a deploy key. After the key is installed, the admin can finish theme installation by pressing a button on the theme page.
428 lines
12 KiB
JavaScript
428 lines
12 KiB
JavaScript
import { COMPONENTS, THEMES } from "admin/models/theme";
|
|
import {
|
|
empty,
|
|
filterBy,
|
|
mapBy,
|
|
match,
|
|
notEmpty,
|
|
} from "@ember/object/computed";
|
|
import Controller from "@ember/controller";
|
|
import EmberObject from "@ember/object";
|
|
import I18n from "I18n";
|
|
import ThemeSettings from "admin/models/theme-settings";
|
|
import bootbox from "bootbox";
|
|
import discourseComputed from "discourse-common/utils/decorators";
|
|
import { makeArray } from "discourse-common/lib/helpers";
|
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
import showModal from "discourse/lib/show-modal";
|
|
import { url } from "discourse/lib/computed";
|
|
|
|
const THEME_UPLOAD_VAR = 2;
|
|
|
|
export default Controller.extend({
|
|
downloadUrl: url("model.id", "/admin/customize/themes/%@/export"),
|
|
previewUrl: url("model.id", "/admin/themes/%@/preview"),
|
|
addButtonDisabled: empty("selectedChildThemeId"),
|
|
editRouteName: "adminCustomizeThemes.edit",
|
|
parentThemesNames: mapBy("model.parentThemes", "name"),
|
|
availableParentThemes: filterBy("allThemes", "component", false),
|
|
availableActiveParentThemes: filterBy("availableParentThemes", "isActive"),
|
|
availableThemesNames: mapBy("availableParentThemes", "name"),
|
|
availableActiveThemesNames: mapBy("availableActiveParentThemes", "name"),
|
|
availableActiveChildThemes: filterBy("availableChildThemes", "hasParents"),
|
|
availableComponentsNames: mapBy("availableChildThemes", "name"),
|
|
availableActiveComponentsNames: mapBy("availableActiveChildThemes", "name"),
|
|
childThemesNames: mapBy("model.childThemes", "name"),
|
|
extraFiles: filterBy("model.theme_fields", "target", "extra_js"),
|
|
|
|
@discourseComputed("model.component", "model.remote_theme")
|
|
showCheckboxes() {
|
|
return !this.model.component || this.model.remote_theme;
|
|
},
|
|
|
|
@discourseComputed("model.editedFields")
|
|
editedFieldsFormatted() {
|
|
const descriptions = [];
|
|
["common", "desktop", "mobile"].forEach((target) => {
|
|
const fields = this.editedFieldsForTarget(target);
|
|
if (fields.length < 1) {
|
|
return;
|
|
}
|
|
let resultString = I18n.t("admin.customize.theme." + target);
|
|
const formattedFields = fields
|
|
.map((f) => I18n.t("admin.customize.theme." + f.name + ".text"))
|
|
.join(" , ");
|
|
resultString += `: ${formattedFields}`;
|
|
descriptions.push(resultString);
|
|
});
|
|
return descriptions;
|
|
},
|
|
|
|
@discourseComputed("colorSchemeId", "model.color_scheme_id")
|
|
colorSchemeChanged(colorSchemeId, existingId) {
|
|
colorSchemeId = colorSchemeId === null ? null : parseInt(colorSchemeId, 10);
|
|
return colorSchemeId !== existingId;
|
|
},
|
|
|
|
@discourseComputed("availableChildThemes", "model.childThemes.[]", "model")
|
|
selectableChildThemes(available, childThemes) {
|
|
if (available) {
|
|
const themes = !childThemes
|
|
? available
|
|
: available.filter((theme) => !childThemes.includes(theme));
|
|
return themes.length === 0 ? null : themes;
|
|
}
|
|
},
|
|
|
|
@discourseComputed("model.parentThemes.[]")
|
|
relativesSelectorSettingsForComponent() {
|
|
return EmberObject.create({
|
|
list_type: "compact",
|
|
type: "list",
|
|
preview: null,
|
|
anyValue: false,
|
|
setting: "parent_theme_ids",
|
|
label: I18n.t("admin.customize.theme.component_on_themes"),
|
|
choices: this.availableThemesNames,
|
|
default: this.parentThemesNames.join("|"),
|
|
value: this.parentThemesNames.join("|"),
|
|
defaultValues: this.availableActiveThemesNames.join("|"),
|
|
allThemes: this.allThemes,
|
|
setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all_themes"),
|
|
});
|
|
},
|
|
|
|
@discourseComputed("model.parentThemes.[]")
|
|
relativesSelectorSettingsForTheme() {
|
|
return EmberObject.create({
|
|
list_type: "compact",
|
|
type: "list",
|
|
preview: null,
|
|
anyValue: false,
|
|
setting: "child_theme_ids",
|
|
label: I18n.t("admin.customize.theme.included_components"),
|
|
choices: this.availableComponentsNames,
|
|
default: this.childThemesNames.join("|"),
|
|
value: this.childThemesNames.join("|"),
|
|
defaultValues: this.availableActiveComponentsNames.join("|"),
|
|
allThemes: this.allThemes,
|
|
setDefaultValuesLabel: I18n.t("admin.customize.theme.add_all"),
|
|
});
|
|
},
|
|
|
|
@discourseComputed("allThemes", "model.component", "model")
|
|
availableChildThemes(allThemes) {
|
|
if (!this.get("model.component")) {
|
|
const themeId = this.get("model.id");
|
|
return allThemes.filter(
|
|
(theme) => theme.get("id") !== themeId && theme.get("component")
|
|
);
|
|
}
|
|
},
|
|
|
|
@discourseComputed("model.component")
|
|
convertKey(component) {
|
|
const type = component ? "component" : "theme";
|
|
return `admin.customize.theme.convert_${type}`;
|
|
},
|
|
|
|
@discourseComputed("model.component")
|
|
convertIcon(component) {
|
|
return component ? "cube" : "";
|
|
},
|
|
|
|
@discourseComputed("model.component")
|
|
convertTooltip(component) {
|
|
const type = component ? "component" : "theme";
|
|
return `admin.customize.theme.convert_${type}_tooltip`;
|
|
},
|
|
|
|
@discourseComputed("model.settings")
|
|
settings(settings) {
|
|
return settings.map((setting) => ThemeSettings.create(setting));
|
|
},
|
|
|
|
hasSettings: notEmpty("settings"),
|
|
|
|
@discourseComputed("model.translations")
|
|
translations(translations) {
|
|
return translations.map((setting) =>
|
|
ThemeSettings.create({ ...setting, textarea: true })
|
|
);
|
|
},
|
|
|
|
hasTranslations: notEmpty("translations"),
|
|
|
|
@discourseComputed(
|
|
"model.remote_theme.local_version",
|
|
"model.remote_theme.remote_version",
|
|
"model.remote_theme.commits_behind"
|
|
)
|
|
hasOverwrittenHistory(localVersion, remoteVersion, commitsBehind) {
|
|
return localVersion !== remoteVersion && commitsBehind === -1;
|
|
},
|
|
|
|
@discourseComputed("model.remoteError", "updatingRemote")
|
|
showRemoteError(errorMessage, updating) {
|
|
return errorMessage && !updating;
|
|
},
|
|
|
|
@discourseComputed(
|
|
"model.remote_theme.remote_url",
|
|
"model.remote_theme.local_version",
|
|
"model.remote_theme.commits_behind"
|
|
)
|
|
finishInstall(remoteUrl, localVersion, commitsBehind) {
|
|
return remoteUrl && !localVersion && !commitsBehind;
|
|
},
|
|
|
|
editedFieldsForTarget(target) {
|
|
return this.get("model.editedFields").filter(
|
|
(field) => field.target === target
|
|
);
|
|
},
|
|
|
|
commitSwitchType() {
|
|
const model = this.model;
|
|
const newValue = !model.get("component");
|
|
model.set("component", newValue);
|
|
|
|
if (newValue) {
|
|
this.set("parentController.currentTab", COMPONENTS);
|
|
} else {
|
|
this.set("parentController.currentTab", THEMES);
|
|
}
|
|
|
|
model
|
|
.saveChanges("component")
|
|
.then(() => {
|
|
this.set("colorSchemeId", null);
|
|
|
|
model.setProperties({
|
|
default: false,
|
|
color_scheme_id: null,
|
|
user_selectable: false,
|
|
child_themes: [],
|
|
childThemes: [],
|
|
});
|
|
|
|
this.get("parentController.model.content").forEach((theme) => {
|
|
const children = makeArray(theme.get("childThemes"));
|
|
const rawChildren = makeArray(theme.get("child_themes"));
|
|
const index = children ? children.indexOf(model) : -1;
|
|
if (index > -1) {
|
|
children.splice(index, 1);
|
|
rawChildren.splice(index, 1);
|
|
theme.setProperties({
|
|
childThemes: children,
|
|
child_themes: rawChildren,
|
|
});
|
|
}
|
|
});
|
|
})
|
|
.catch(popupAjaxError);
|
|
},
|
|
transitionToEditRoute() {
|
|
this.transitionToRoute(
|
|
this.editRouteName,
|
|
this.get("model.id"),
|
|
"common",
|
|
"scss"
|
|
);
|
|
},
|
|
sourceIsHttp: match("model.remote_theme.remote_url", /^http(s)?:\/\//),
|
|
|
|
@discourseComputed(
|
|
"model.remote_theme.remote_url",
|
|
"model.remote_theme.branch"
|
|
)
|
|
remoteThemeLink(remoteThemeUrl, remoteThemeBranch) {
|
|
return remoteThemeBranch
|
|
? `${remoteThemeUrl.replace(/\.git$/, "")}/tree/${remoteThemeBranch}`
|
|
: remoteThemeUrl;
|
|
},
|
|
|
|
@discourseComputed("model.user.id", "model.default")
|
|
showConvert(userId, defaultTheme) {
|
|
return userId > 0 && !defaultTheme;
|
|
},
|
|
|
|
actions: {
|
|
updateToLatest() {
|
|
this.set("updatingRemote", true);
|
|
this.model
|
|
.updateToLatest()
|
|
.catch(popupAjaxError)
|
|
.finally(() => {
|
|
this.set("updatingRemote", false);
|
|
});
|
|
},
|
|
|
|
checkForThemeUpdates() {
|
|
this.set("updatingRemote", true);
|
|
this.model
|
|
.checkForUpdates()
|
|
.catch(popupAjaxError)
|
|
.finally(() => {
|
|
this.set("updatingRemote", false);
|
|
});
|
|
},
|
|
|
|
addUploadModal() {
|
|
showModal("admin-add-upload", { admin: true, name: "" });
|
|
},
|
|
|
|
addUpload(info) {
|
|
let model = this.model;
|
|
model.setField("common", info.name, "", info.upload_id, THEME_UPLOAD_VAR);
|
|
model.saveChanges("theme_fields").catch((e) => popupAjaxError(e));
|
|
},
|
|
|
|
cancelChangeScheme() {
|
|
this.set("colorSchemeId", this.get("model.color_scheme_id"));
|
|
},
|
|
changeScheme() {
|
|
let schemeId = this.colorSchemeId;
|
|
this.set(
|
|
"model.color_scheme_id",
|
|
schemeId === null ? null : parseInt(schemeId, 10)
|
|
);
|
|
this.model.saveChanges("color_scheme_id");
|
|
},
|
|
startEditingName() {
|
|
this.set("oldName", this.get("model.name"));
|
|
this.set("editingName", true);
|
|
},
|
|
cancelEditingName() {
|
|
this.set("model.name", this.oldName);
|
|
this.set("editingName", false);
|
|
},
|
|
finishedEditingName() {
|
|
this.model.saveChanges("name");
|
|
this.set("editingName", false);
|
|
},
|
|
|
|
editTheme() {
|
|
if (this.get("model.remote_theme.is_git")) {
|
|
bootbox.confirm(
|
|
I18n.t("admin.customize.theme.edit_confirm"),
|
|
(result) => {
|
|
if (result) {
|
|
this.transitionToEditRoute();
|
|
}
|
|
}
|
|
);
|
|
} else {
|
|
this.transitionToEditRoute();
|
|
}
|
|
},
|
|
|
|
applyDefault() {
|
|
const model = this.model;
|
|
model.saveChanges("default").then(() => {
|
|
if (model.get("default")) {
|
|
this.allThemes.forEach((theme) => {
|
|
if (theme !== model && theme.get("default")) {
|
|
theme.set("default", false);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
applyUserSelectable() {
|
|
this.model.saveChanges("user_selectable");
|
|
},
|
|
|
|
applyAutoUpdateable() {
|
|
this.model.saveChanges("auto_update");
|
|
},
|
|
|
|
addChildTheme() {
|
|
let themeId = parseInt(this.selectedChildThemeId, 10);
|
|
let theme = this.allThemes.findBy("id", themeId);
|
|
this.model.addChildTheme(theme).then(() => this.store.findAll("theme"));
|
|
},
|
|
|
|
removeUpload(upload) {
|
|
return bootbox.confirm(
|
|
I18n.t("admin.customize.theme.delete_upload_confirm"),
|
|
I18n.t("no_value"),
|
|
I18n.t("yes_value"),
|
|
(result) => {
|
|
if (result) {
|
|
this.model.removeField(upload);
|
|
}
|
|
}
|
|
);
|
|
},
|
|
|
|
removeChildTheme(theme) {
|
|
this.model
|
|
.removeChildTheme(theme)
|
|
.then(() => this.store.findAll("theme"));
|
|
},
|
|
|
|
destroy() {
|
|
return bootbox.confirm(
|
|
I18n.t("admin.customize.delete_confirm", {
|
|
theme_name: this.get("model.name"),
|
|
}),
|
|
I18n.t("no_value"),
|
|
I18n.t("yes_value"),
|
|
(result) => {
|
|
if (result) {
|
|
const model = this.model;
|
|
model.setProperties({ recentlyInstalled: false });
|
|
model.destroyRecord().then(() => {
|
|
this.allThemes.removeObject(model);
|
|
this.transitionToRoute("adminCustomizeThemes");
|
|
});
|
|
}
|
|
}
|
|
);
|
|
},
|
|
|
|
switchType() {
|
|
const relatives = this.get("model.component")
|
|
? this.get("model.parentThemes")
|
|
: this.get("model.childThemes");
|
|
|
|
let message = I18n.t(`${this.convertKey}_alert_generic`);
|
|
|
|
if (relatives && relatives.length > 0) {
|
|
message = I18n.t(`${this.convertKey}_alert`, {
|
|
relatives: relatives
|
|
.map((relative) => relative.get("name"))
|
|
.join(", "),
|
|
});
|
|
}
|
|
|
|
bootbox.confirm(
|
|
message,
|
|
I18n.t("no_value"),
|
|
I18n.t("yes_value"),
|
|
(result) => {
|
|
if (result) {
|
|
this.commitSwitchType();
|
|
}
|
|
}
|
|
);
|
|
},
|
|
|
|
enableComponent() {
|
|
this.model.set("enabled", true);
|
|
this.model
|
|
.saveChanges("enabled")
|
|
.catch(() => this.model.set("enabled", false));
|
|
},
|
|
|
|
disableComponent() {
|
|
this.model.set("enabled", false);
|
|
this.model
|
|
.saveChanges("enabled")
|
|
.catch(() => this.model.set("enabled", true));
|
|
},
|
|
},
|
|
});
|