Installing multiple copies of the same theme/component is possible, but you rarely need to actually have multiple copies installed. We've seen many times new admins installing duplicates of components because they were unaware it was already installed. This PR makes the theme installer modal loop through the existing themes when you click on 'install', and if there is a theme with a URL that matches the URL you entered, a warning will show up and you will need to click 'install' again to proceed.
224 lines
6.1 KiB
JavaScript
224 lines
6.1 KiB
JavaScript
import { COMPONENTS, THEMES } from "admin/models/theme";
|
|
import Controller, { inject as controller } from "@ember/controller";
|
|
import { alias, equal, match } from "@ember/object/computed";
|
|
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
|
import I18n from "I18n";
|
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
|
import { POPULAR_THEMES } from "discourse-common/helpers/popular-themes";
|
|
import { ajax } from "discourse/lib/ajax";
|
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
import { set } from "@ember/object";
|
|
|
|
const MIN_NAME_LENGTH = 4;
|
|
|
|
export default Controller.extend(ModalFunctionality, {
|
|
adminCustomizeThemes: controller(),
|
|
themesController: controller("adminCustomizeThemes"),
|
|
popular: equal("selection", "popular"),
|
|
local: equal("selection", "local"),
|
|
remote: equal("selection", "remote"),
|
|
create: equal("selection", "create"),
|
|
selection: "popular",
|
|
loading: false,
|
|
keyGenUrl: "/admin/themes/generate_key_pair",
|
|
importUrl: "/admin/themes/import",
|
|
recordType: "theme",
|
|
checkPrivate: match("uploadUrl", /^git/),
|
|
localFile: null,
|
|
uploadUrl: null,
|
|
urlPlaceholder: "https://github.com/discourse/sample_theme",
|
|
advancedVisible: false,
|
|
selectedType: alias("themesController.currentTab"),
|
|
component: equal("selectedType", COMPONENTS),
|
|
|
|
init() {
|
|
this._super(...arguments);
|
|
|
|
this.createTypes = [
|
|
{ name: I18n.t("admin.customize.theme.theme"), value: THEMES },
|
|
{ name: I18n.t("admin.customize.theme.component"), value: COMPONENTS },
|
|
];
|
|
},
|
|
|
|
@discourseComputed("themesController.installedThemes")
|
|
themes(installedThemes) {
|
|
return POPULAR_THEMES.map((t) => {
|
|
if (installedThemes.includes(t.name)) {
|
|
set(t, "installed", true);
|
|
}
|
|
return t;
|
|
});
|
|
},
|
|
|
|
@discourseComputed(
|
|
"loading",
|
|
"remote",
|
|
"uploadUrl",
|
|
"local",
|
|
"localFile",
|
|
"create",
|
|
"nameTooShort"
|
|
)
|
|
installDisabled(
|
|
isLoading,
|
|
isRemote,
|
|
uploadUrl,
|
|
isLocal,
|
|
localFile,
|
|
isCreate,
|
|
nameTooShort
|
|
) {
|
|
return (
|
|
isLoading ||
|
|
(isRemote && !uploadUrl) ||
|
|
(isLocal && !localFile) ||
|
|
(isCreate && nameTooShort)
|
|
);
|
|
},
|
|
|
|
@observes("privateChecked")
|
|
privateWasChecked() {
|
|
this.privateChecked
|
|
? this.set("urlPlaceholder", "git@github.com:discourse/sample_theme.git")
|
|
: this.set("urlPlaceholder", "https://github.com/discourse/sample_theme");
|
|
|
|
const checked = this.privateChecked;
|
|
if (checked && !this._keyLoading) {
|
|
this._keyLoading = true;
|
|
ajax(this.keyGenUrl, { type: "POST" })
|
|
.then((pair) => {
|
|
this.setProperties({
|
|
privateKey: pair.private_key,
|
|
publicKey: pair.public_key,
|
|
});
|
|
})
|
|
.catch(popupAjaxError)
|
|
.finally(() => {
|
|
this._keyLoading = false;
|
|
});
|
|
}
|
|
},
|
|
|
|
@discourseComputed("name")
|
|
nameTooShort(name) {
|
|
return !name || name.length < MIN_NAME_LENGTH;
|
|
},
|
|
|
|
@discourseComputed("component")
|
|
placeholder(component) {
|
|
if (component) {
|
|
return I18n.t("admin.customize.theme.component_name");
|
|
} else {
|
|
return I18n.t("admin.customize.theme.theme_name");
|
|
}
|
|
},
|
|
|
|
@discourseComputed("selection")
|
|
submitLabel(selection) {
|
|
return `admin.customize.theme.${
|
|
selection === "create" ? "create" : "install"
|
|
}`;
|
|
},
|
|
|
|
@discourseComputed("privateChecked", "checkPrivate", "publicKey")
|
|
showPublicKey(privateChecked, checkPrivate, publicKey) {
|
|
return privateChecked && checkPrivate && publicKey;
|
|
},
|
|
|
|
onClose() {
|
|
this.set("duplicateRemoteThemeWarning", null);
|
|
},
|
|
|
|
themeHasSameUrl(theme, url) {
|
|
const themeUrl = theme.remote_theme && theme.remote_theme.remote_url;
|
|
return (
|
|
themeUrl &&
|
|
url &&
|
|
url.replace(/\.git$/, "") === themeUrl.replace(/\.git$/, "")
|
|
);
|
|
},
|
|
|
|
actions: {
|
|
uploadLocaleFile() {
|
|
this.set("localFile", $("#file-input")[0].files[0]);
|
|
},
|
|
|
|
toggleAdvanced() {
|
|
this.toggleProperty("advancedVisible");
|
|
},
|
|
|
|
installThemeFromList(url) {
|
|
this.set("uploadUrl", url);
|
|
this.send("installTheme");
|
|
},
|
|
|
|
installTheme() {
|
|
if (this.create) {
|
|
this.set("loading", true);
|
|
const theme = this.store.createRecord(this.recordType);
|
|
theme
|
|
.save({ name: this.name, component: this.component })
|
|
.then(() => {
|
|
this.themesController.send("addTheme", theme);
|
|
this.send("closeModal");
|
|
})
|
|
.catch(popupAjaxError)
|
|
.finally(() => this.set("loading", false));
|
|
|
|
return;
|
|
}
|
|
|
|
let options = {
|
|
type: "POST",
|
|
};
|
|
|
|
if (this.local) {
|
|
options.processData = false;
|
|
options.contentType = false;
|
|
options.data = new FormData();
|
|
options.data.append("theme", this.localFile);
|
|
}
|
|
|
|
if (this.remote || this.popular) {
|
|
const duplicate = this.themesController.model.content.find((theme) =>
|
|
this.themeHasSameUrl(theme, this.uploadUrl)
|
|
);
|
|
if (duplicate && !this.duplicateRemoteThemeWarning) {
|
|
const warning = I18n.t(
|
|
"admin.customize.theme.duplicate_remote_theme",
|
|
{ name: duplicate.name }
|
|
);
|
|
this.set("duplicateRemoteThemeWarning", warning);
|
|
return;
|
|
}
|
|
options.data = {
|
|
remote: this.uploadUrl,
|
|
branch: this.branch,
|
|
};
|
|
|
|
if (this.privateChecked) {
|
|
options.data.private_key = this.privateKey;
|
|
}
|
|
}
|
|
|
|
if (this.get("model.user_id")) {
|
|
// Used by theme-creator
|
|
options.data["user_id"] = this.get("model.user_id");
|
|
}
|
|
|
|
this.set("loading", true);
|
|
ajax(this.importUrl, options)
|
|
.then((result) => {
|
|
const theme = this.store.createRecord(this.recordType, result.theme);
|
|
this.adminCustomizeThemes.send("addTheme", theme);
|
|
this.send("closeModal");
|
|
})
|
|
.then(() => {
|
|
this.setProperties({ privateKey: null, publicKey: null });
|
|
})
|
|
.catch(popupAjaxError)
|
|
.finally(() => this.set("loading", false));
|
|
},
|
|
},
|
|
});
|