1262 lines
No EOL
61 KiB
JavaScript
1262 lines
No EOL
61 KiB
JavaScript
/**
|
||
* @name ThemeRepo
|
||
* @author DevilBro
|
||
* @authorId 278543574059057154
|
||
* @version 2.5.6
|
||
* @description Allows you to download all Themes from BD's Website within Discord
|
||
* @invite Jx3TjNS
|
||
* @donate https://www.paypal.me/MircoWittrien
|
||
* @patreon https://www.patreon.com/MircoWittrien
|
||
* @website https://mwittrien.github.io/
|
||
* @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/ThemeRepo/
|
||
* @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/ThemeRepo.plugin.js
|
||
*/
|
||
|
||
module.exports = (_ => {
|
||
const changeLog = {
|
||
|
||
};
|
||
|
||
return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class {
|
||
constructor (meta) {for (let key in meta) this[key] = meta[key];}
|
||
getName () {return this.name;}
|
||
getAuthor () {return this.author;}
|
||
getVersion () {return this.version;}
|
||
getDescription () {return `The Library Plugin needed for ${this.name} is missing. Open the Plugin Settings to download it. \n\n${this.description}`;}
|
||
|
||
downloadLibrary () {
|
||
BdApi.Net.fetch("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js").then(r => {
|
||
if (!r || r.status != 200) throw new Error();
|
||
else return r.text();
|
||
}).then(b => {
|
||
if (!b) throw new Error();
|
||
else return require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.showToast("Finished downloading BDFDB Library", {type: "success"}));
|
||
}).catch(error => {
|
||
BdApi.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library");
|
||
});
|
||
}
|
||
|
||
load () {
|
||
if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []});
|
||
if (!window.BDFDB_Global.downloadModal) {
|
||
window.BDFDB_Global.downloadModal = true;
|
||
BdApi.showConfirmationModal("Library Missing", `The Library Plugin needed for ${this.name} is missing. Please click "Download Now" to install it.`, {
|
||
confirmText: "Download Now",
|
||
cancelText: "Cancel",
|
||
onCancel: _ => {delete window.BDFDB_Global.downloadModal;},
|
||
onConfirm: _ => {
|
||
delete window.BDFDB_Global.downloadModal;
|
||
this.downloadLibrary();
|
||
}
|
||
});
|
||
}
|
||
if (!window.BDFDB_Global.pluginQueue.includes(this.name)) window.BDFDB_Global.pluginQueue.push(this.name);
|
||
}
|
||
start () {this.load();}
|
||
stop () {}
|
||
getSettingsPanel () {
|
||
let template = document.createElement("template");
|
||
template.innerHTML = `<div style="color: var(--header-primary); font-size: 16px; font-weight: 300; white-space: pre; line-height: 22px;">The Library Plugin needed for ${this.name} is missing.\nPlease click <a style="font-weight: 500;">Download Now</a> to install it.</div>`;
|
||
template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary);
|
||
return template.content.firstElementChild;
|
||
}
|
||
} : (([Plugin, BDFDB]) => {
|
||
var _this;
|
||
|
||
var list;
|
||
|
||
var loading, cachedThemes, grabbedThemes, generatorThemes, updateInterval, errorState;
|
||
var searchString, searchTimeout, forcedSort, forcedOrder, showOnlyOutdated;
|
||
var updateGeneratorTimeout, forceRerenderGenerator, nativeCSS, nativeCSSvars;
|
||
|
||
var favorites = [];
|
||
|
||
const themeStates = {
|
||
INSTALLED: 0,
|
||
OUTDATED: 1,
|
||
DOWNLOADABLE: 2
|
||
};
|
||
const buttonData = {
|
||
INSTALLED: {
|
||
backgroundColor: "var(--status-positive)",
|
||
icon: "CHECKMARK",
|
||
text: "installed"
|
||
},
|
||
OUTDATED: {
|
||
backgroundColor: "var(--status-danger)",
|
||
icon: "CLOSE",
|
||
text: "outdated"
|
||
},
|
||
DOWNLOADABLE: {
|
||
backgroundColor: "var(--bdfdb-blurple)",
|
||
icon: "DOWNLOAD",
|
||
text: "download"
|
||
}
|
||
};
|
||
const reverseSorts = [
|
||
"RELEASEDATE", "DOWNLOADS", "LIKES", "FAV"
|
||
];
|
||
const sortKeys = {
|
||
NAME: "Name",
|
||
AUTHORNAME: "Author",
|
||
VERSION: "Version",
|
||
DESCRIPTION: "Description",
|
||
RELEASEDATE: "Release Date",
|
||
STATE: "Update State",
|
||
DOWNLOADS: "Downloads",
|
||
LIKES: "Likes",
|
||
FAV: "Favorites"
|
||
};
|
||
const orderKeys = {
|
||
ASC: "ascending",
|
||
DESC: "descending"
|
||
};
|
||
|
||
const themeRepoIcon = `<svg width="42" height="32" viewBox="0 0 42 32"><path fill="COLOR_1" d="M 0,0 V 7.671875 H 8.6211458 V 32 H 16.922769 V 7.672 l 8.621146,-1.25e-4 V 0 Z"/><path fill="COLOR_2" d="M 29.542969 0 L 29.542969 7.5488281 L 30.056641 7.5488281 C 35.246318 7.5488281 35.246318 14.869141 30.056641 14.869141 L 25.234375 14.869141 L 25.234375 11.671875 L 20.921875 11.671875 L 20.921875 32 L 25.234375 32 L 25.234375 21.830078 L 26.705078 21.830078 L 34.236328 32 L 42 32 L 42 28.931641 L 35.613281 21.017578 C 39.562947 19.797239 41.998047 16.452154 41.998047 10.53125 C 41.814341 3.0284252 36.625168 0 29.919922 0 L 29.542969 0 z"/></svg>`;
|
||
|
||
const RepoListComponent = class ThemeList extends BdApi.React.Component {
|
||
componentDidMount() {
|
||
list = this;
|
||
BDFDB.TimeUtils.timeout(_ => {
|
||
forcedSort = null;
|
||
forcedOrder = null;
|
||
showOnlyOutdated = false;
|
||
}, 5000);
|
||
}
|
||
componentWillUnmount() {
|
||
list = null;
|
||
}
|
||
filterThemes() {
|
||
let themes = grabbedThemes.map(theme => {
|
||
if (theme.failed) return;
|
||
const installedTheme = _this.getInstalledTheme(theme);
|
||
const state = installedTheme ? (theme.version && _this.compareVersions(theme.version, _this.getString(installedTheme.version)) ? themeStates.OUTDATED : themeStates.INSTALLED) : themeStates.DOWNLOADABLE;
|
||
return Object.assign(theme, {
|
||
search: [theme.name, theme.version, theme.authorname, theme.description, theme.tags].flat(10).filter(n => typeof n == "string").join(" ").toUpperCase(),
|
||
description: theme.description || "No Description found",
|
||
fav: favorites.includes(theme.id) && 1,
|
||
new: state == themeStates.DOWNLOADABLE && !cachedThemes.includes(theme.id) && 1,
|
||
state: state
|
||
});
|
||
}).filter(n => n);
|
||
if (!this.props.updated) themes = themes.filter(theme => theme.state != themeStates.INSTALLED);
|
||
if (!this.props.outdated) themes = themes.filter(theme => theme.state != themeStates.OUTDATED);
|
||
if (!this.props.downloadable) themes = themes.filter(theme => theme.state != themeStates.DOWNLOADABLE);
|
||
if (searchString) {
|
||
let usedSearchString = searchString.toUpperCase();
|
||
let spacelessUsedSearchString = usedSearchString.replace(/\s/g, "");
|
||
themes = themes.filter(theme => theme.search.indexOf(usedSearchString) > -1 || theme.search.indexOf(spacelessUsedSearchString) > -1);
|
||
}
|
||
|
||
BDFDB.ArrayUtils.keySort(themes, this.props.sortKey.toLowerCase());
|
||
if (this.props.orderKey == "DESC") themes.reverse();
|
||
if (reverseSorts.includes(this.props.sortKey)) themes.reverse();
|
||
return themes;
|
||
}
|
||
createThemeFile(name, filename, body, autoloadKey) {
|
||
return new Promise(callback => BDFDB.LibraryRequires.fs.writeFile(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getThemesFolder(), filename), body, error => {
|
||
if (error) {
|
||
callback(true);
|
||
BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_fail", `Theme "${name}"`), {type: "danger"});
|
||
}
|
||
else {
|
||
BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_success", `Theme "${name}"`), {type: "success"});
|
||
if (_this.settings.general[autoloadKey]) BDFDB.TimeUtils.timeout(_ => {
|
||
if (BDFDB.BDUtils.isThemeEnabled(name) == false) {
|
||
BDFDB.BDUtils.enableTheme(name, false);
|
||
BDFDB.LogUtils.log(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_started", name), _this);
|
||
}
|
||
}, 3000);
|
||
callback();
|
||
}
|
||
}));
|
||
}
|
||
generateTheme(css) {
|
||
if (!css || !BDFDB.ObjectUtils.is(this.props.generatorValues)) return "";
|
||
for (let inputId in this.props.generatorValues) if (this.props.generatorValues[inputId].value && this.props.generatorValues[inputId].value.trim() && this.props.generatorValues[inputId].value != this.props.generatorValues[inputId].oldValue) css = css.replace(new RegExp(`--${BDFDB.StringUtils.regEscape(inputId)}(\\s*):(\\s*)${BDFDB.StringUtils.regEscape(this.props.generatorValues[inputId].oldValue)}`,"g"),`--${inputId}$1: $2${this.props.generatorValues[inputId].value}`);
|
||
return css;
|
||
}
|
||
createFixerCSS(body) {
|
||
let oldCSS = body.replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r").split("REPLACE_CLASS_");
|
||
let newCSS = oldCSS.shift();
|
||
for (let str of oldCSS) {
|
||
let reg = /([A-z0-9_]+)(.*)/.exec(str);
|
||
newCSS += BDFDB.dotCN[reg[1]] + reg[2];
|
||
}
|
||
return newCSS.replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\r/g, "\r");
|
||
}
|
||
render() {
|
||
if (!this.props.tab) this.props.tab = "Themes";
|
||
|
||
const entries = (!loading.is && grabbedThemes.length ? this.filterThemes() : []);
|
||
|
||
const emptyState = errorState ? {
|
||
lightSrc: "/assets/d6dfb89ab06b62044dbb.svg",
|
||
darkSrc: "/assets/8eeb59bba0a61cbffc41.svg",
|
||
text: "Could not load Theme Store due to an Issue with the BD Website"
|
||
} : !entries.length && !this.props.updated && !this.props.outdated && !this.props.downloadable ? {
|
||
text: `You disabled all Filter Options in the "${BDFDB.LanguageUtils.LanguageStrings.SETTINGS}" Tab`
|
||
} : !entries.length && searchString ? {
|
||
lightSrc: "/assets/75081bdaad2d359c1469.svg",
|
||
darkSrc: "/assets/45cd76fed34c8e398cc8.svg"
|
||
} : !entries.length ? {} : null;
|
||
|
||
if (forceRerenderGenerator && this.props.tab == "Generator") BDFDB.TimeUtils.timeout(_ => {
|
||
forceRerenderGenerator = false;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
});
|
||
|
||
return BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN._repo,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCNS._repolistheader + BDFDB.disCN.settingswindowcontentcolumndefault,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
|
||
className: BDFDB.disCN.marginbottom4,
|
||
align: BDFDB.LibraryComponents.Flex.Align.CENTER,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
|
||
grow: 1,
|
||
shrink: 0,
|
||
children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormTitle, {
|
||
tag: BDFDB.LibraryComponents.FormComponents.FormTags.H1,
|
||
className: BDFDB.disCN.marginreset,
|
||
children: `Theme Repo — ${loading.is ? 0 : entries.length || 0}/${loading.is ? 0 : grabbedThemes.length}`
|
||
})
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
|
||
children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SearchBar, {
|
||
autoFocus: true,
|
||
query: searchString,
|
||
onChange: value => {
|
||
if (loading.is) return;
|
||
BDFDB.TimeUtils.clear(searchTimeout);
|
||
searchTimeout = BDFDB.TimeUtils.timeout(_ => {
|
||
searchString = value.replace(/[<|>]/g, "");
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}, 1000);
|
||
},
|
||
onClear: _ => {
|
||
if (loading.is) return;
|
||
searchString = "";
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}
|
||
})
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, {
|
||
size: BDFDB.LibraryComponents.Button.Sizes.TINY,
|
||
children: BDFDB.LanguageUtils.LibraryStrings.check_for_updates,
|
||
onClick: _ => {
|
||
if (loading.is) return;
|
||
loading = {is: false, timeout: null, amount: 0};
|
||
_this.loadThemes(true);
|
||
}
|
||
})
|
||
]
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
|
||
className: BDFDB.disCNS.tabbarcontainer + BDFDB.disCN.tabbarcontainerbottom,
|
||
align: BDFDB.LibraryComponents.Flex.Align.CENTER,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
|
||
children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TabBar, {
|
||
className: BDFDB.disCN.tabbar,
|
||
itemClassName: BDFDB.disCN.tabbaritem,
|
||
type: BDFDB.LibraryComponents.TabBar.Types.TOP,
|
||
selectedItem: this.props.tab,
|
||
items: [{value: "Themes"}, {value: "Generator"}, {value: BDFDB.LanguageUtils.LanguageStrings.SETTINGS}],
|
||
onItemSelect: value => {
|
||
this.props.tab = value;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}
|
||
})
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
|
||
children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.QuickSelect, {
|
||
label: BDFDB.LanguageUtils.LibraryStrings.sort_by + ":",
|
||
value: {
|
||
label: sortKeys[this.props.sortKey],
|
||
value: this.props.sortKey
|
||
},
|
||
options: Object.keys(sortKeys).map(key => ({
|
||
label: sortKeys[key],
|
||
value: key
|
||
})),
|
||
onChange: key => {
|
||
this.props.sortKey = key;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}
|
||
})
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
|
||
children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.QuickSelect, {
|
||
label: BDFDB.LanguageUtils.LibraryStrings.order + ":",
|
||
value: {
|
||
label: BDFDB.LanguageUtils.LibraryStrings[orderKeys[this.props.orderKey]],
|
||
value: this.props.orderKey
|
||
},
|
||
options: Object.keys(orderKeys).map(key => ({
|
||
label: BDFDB.LanguageUtils.LibraryStrings[orderKeys[key]],
|
||
value: key
|
||
})),
|
||
onChange: key => {
|
||
this.props.orderKey = key;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}
|
||
})
|
||
})
|
||
]
|
||
})
|
||
]
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Scrollers.Auto, {
|
||
className: BDFDB.disCNS._repolistscroller + BDFDB.disCN.settingswindowcontentcolumndefault,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, {
|
||
tab: "Themes",
|
||
open: this.props.tab == "Themes",
|
||
render: false,
|
||
children: loading.is ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
|
||
direction: BDFDB.LibraryComponents.Flex.Direction.VERTICAL,
|
||
justify: BDFDB.LibraryComponents.Flex.Justify.CENTER,
|
||
style: {marginTop: "50%"},
|
||
children: [
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SpinnerComponents.Spinner, {
|
||
type: BDFDB.LibraryComponents.SpinnerComponents.Types.WANDERING_CUBES
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextElement, {
|
||
className: BDFDB.disCN.margintop20,
|
||
style: {textAlign: "center"},
|
||
children: `${BDFDB.LanguageUtils.LibraryStringsFormat("loading", "Theme Repo")} - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}`
|
||
})
|
||
]
|
||
}) : emptyState ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.EmptyStateImage, emptyState) : BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycards,
|
||
children: entries.map(theme => BDFDB.ReactUtils.createElement(RepoCardComponent, {
|
||
data: theme
|
||
})).filter(n => n)
|
||
})
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, {
|
||
tab: "Generator",
|
||
open: this.props.tab == "Generator",
|
||
render: false,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, {
|
||
type: "Select",
|
||
margin: 20,
|
||
label: "Choose a Generator Theme",
|
||
basis: "60%",
|
||
value: this.props.currentGenerator || "-----",
|
||
options: [{value: "-----", label: "-----"}, nativeCSSvars && {value: "nativediscord", label: "Discord"}].concat(generatorThemes.map(t => ({value: t.id, label: t.name || "-----"})).sort((x, y) => (x.label < y.label ? -1 : x.label > y.label ? 1 : 0))).filter(n => n),
|
||
onChange: value => {
|
||
let generatorTheme = generatorThemes.find(t => t.id == value);
|
||
if (generatorTheme || value == "nativediscord") {
|
||
if (this.props.currentGenerator) forceRerenderGenerator = true;
|
||
this.props.currentGenerator = value;
|
||
this.props.currentGeneratorIsNative = value == "nativediscord";
|
||
this.props.generatorValues = {};
|
||
}
|
||
else {
|
||
delete this.props.currentGenerator;
|
||
delete this.props.currentGeneratorIsNative;
|
||
delete this.props.generatorValues;
|
||
}
|
||
delete this.props.currentTheme;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}
|
||
}),
|
||
!this.props.currentGenerator ? null : (forceRerenderGenerator ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
|
||
direction: BDFDB.LibraryComponents.Flex.Direction.VERTICAL,
|
||
justify: BDFDB.LibraryComponents.Flex.Justify.CENTER,
|
||
style: {marginTop: "50%"},
|
||
children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SpinnerComponents.Spinner, {
|
||
type: BDFDB.LibraryComponents.SpinnerComponents.Types.WANDERING_CUBES
|
||
})
|
||
}) : [
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, {
|
||
className: BDFDB.disCN.marginbottom20,
|
||
type: "Button",
|
||
label: "Download generated Theme",
|
||
children: "Download",
|
||
onClick: _ => {
|
||
if (this.props.currentGeneratorIsNative) {
|
||
this.createThemeFile("Discord", "Discord.theme.css", `/**\n * @name Discord\n * @description Allow you to easily customize Discord's native Look \n * @author DevilBro\n * @version 1.0.0\n * @authorId 278543574059057154\n * @invite Jx3TjNS\n * @donate https://www.paypal.me/MircoWittrien\n * @patreon https://www.patreon.com/MircoWittrien\n */\n\n` + this.generateTheme(nativeCSSvars), "startDownloaded");
|
||
}
|
||
else {
|
||
let generatorTheme = generatorThemes.find(t => t.id == this.props.currentGenerator);
|
||
if (generatorTheme) this.createThemeFile(generatorTheme.name, generatorTheme.rawSourceUrl.split("/").pop(), this.generateTheme(generatorTheme.fullCSS), "startDownloaded");
|
||
}
|
||
}
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, {
|
||
className: BDFDB.disCN.marginbottom20
|
||
}),
|
||
(_ => {
|
||
let generatorTheme = generatorThemes.find(t => t.id == this.props.currentGenerator);
|
||
let vars = this.props.currentGeneratorIsNative ? nativeCSSvars.split(".theme-dark, .theme-light") : ((generatorTheme || {}).fullCSS || "").split(":root");
|
||
if (vars.length < 2) return null;
|
||
vars = vars[1].replace(/\t\(/g, " (").replace(/\r|\t| {2,}/g, "").replace(/\/\*\n*((?!\/\*|\*\/).|\n)*\n+((?!\/\*|\*\/).|\n)*\n*\*\//g, "").replace(/\n\/\*.*?\*\//g, "").replace(/\n/g, "");
|
||
vars = vars.split("{");
|
||
vars.shift();
|
||
vars = vars.join("{").replace(/\s*(:|;|--|\*)\s*/g, "$1");
|
||
vars = vars.split("}")[0];
|
||
vars = (vars.endsWith(";") ? vars.slice(0, -1) : vars).slice(2).split(/;--|\*\/--/);
|
||
let inputRefs = [];
|
||
for (let varStr of vars) {
|
||
varStr = varStr.split(":");
|
||
let varName = varStr.shift().trim();
|
||
varStr = varStr.join(":").split(/;[^A-z0-9]|\/\*/);
|
||
let oldValue = varStr.shift().trim();
|
||
if (oldValue) {
|
||
let childType = "text", childMode = "";
|
||
let isColor = BDFDB.ColorUtils.getType(oldValue);
|
||
let isComp = !isColor && /^[0-9 ]+,[0-9 ]+,[0-9 ]+$/g.test(oldValue);
|
||
if (isColor || isComp) {
|
||
childType = "color";
|
||
childMode = isComp && "comp";
|
||
}
|
||
else {
|
||
let isUrlFile = /url\(.+\)/gi.test(oldValue);
|
||
let isFile = !isUrlFile && /(http(s)?):\/\/[(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/.test(oldValue);
|
||
if (isFile || isUrlFile) {
|
||
childType = "file";
|
||
childMode = isUrlFile && "url";
|
||
}
|
||
}
|
||
let varDescription = varStr.join("").replace(/\*\/|\/\*/g, "").replace(/:/g, ": ").replace(/: \//g, ":/").replace(/--/g, " --").replace(/\( --/g, "(--").trim();
|
||
this.props.generatorValues[varName] = {value: oldValue, oldValue};
|
||
inputRefs.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, {
|
||
dividerBottom: vars[vars.length-1] != varStr,
|
||
type: "TextInput",
|
||
childProps: {
|
||
type: childType,
|
||
mode: childMode,
|
||
filter: childType == "file" && "image"
|
||
},
|
||
label: varName.split("-").map(BDFDB.StringUtils.upperCaseFirstChar).join(" "),
|
||
note: varDescription && varDescription.indexOf("*") == 0 ? varDescription.slice(1) : varDescription,
|
||
basis: "70%",
|
||
value: oldValue,
|
||
placeholder: oldValue,
|
||
onChange: value => {
|
||
BDFDB.TimeUtils.clear(updateGeneratorTimeout);
|
||
updateGeneratorTimeout = BDFDB.TimeUtils.timeout(_ => this.props.generatorValues[varName] = {value, oldValue}, 1000);
|
||
}
|
||
}));
|
||
}
|
||
}
|
||
return inputRefs;
|
||
})()
|
||
])
|
||
].flat(10).filter(n => n)
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, {
|
||
tab: BDFDB.LanguageUtils.LanguageStrings.SETTINGS,
|
||
open: this.props.tab == BDFDB.LanguageUtils.LanguageStrings.SETTINGS,
|
||
render: false,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, {
|
||
title: "Shows following Themes",
|
||
children: Object.keys(_this.defaults.filters).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
|
||
type: "Switch",
|
||
plugin: _this,
|
||
keys: ["filters", key],
|
||
label: _this.defaults.filters[key].description,
|
||
value: _this.settings.filters[key],
|
||
onChange: value => {
|
||
this.props[key] = _this.settings.filters[key] = value;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}
|
||
}))
|
||
}),
|
||
Object.keys(_this.defaults.general).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
|
||
type: "Switch",
|
||
plugin: _this,
|
||
keys: ["general", key],
|
||
label: _this.defaults.general[key].description,
|
||
value: _this.settings.general[key],
|
||
onChange: value => {
|
||
_this.settings.general[key] = value;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}
|
||
}))
|
||
].flat(10).filter(n => n)
|
||
})
|
||
]
|
||
})
|
||
]
|
||
});
|
||
}
|
||
};
|
||
|
||
const RepoCardComponent = class ThemeCard extends BdApi.React.Component {
|
||
render() {
|
||
return BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycard,
|
||
onMouseEnter: _ => {
|
||
this.props.hovered = true;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
},
|
||
children: [
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardheader,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardcoverwrapper,
|
||
children: [
|
||
this.props.data.thumbnailUrl && this.props.hovered && BDFDB.ReactUtils.createElement("img", {
|
||
className: BDFDB.disCN.discoverycardcover,
|
||
src: this.props.data.thumbnailUrl,
|
||
onClick: _ => {
|
||
const url = this.props.data.thumbnailUrl;
|
||
const img = document.createElement("img");
|
||
img.addEventListener("load", function() {
|
||
BDFDB.LibraryModules.ModalUtils.openModal(modalData => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalRoot, Object.assign({
|
||
className: BDFDB.disCN.imagemodal
|
||
}, modalData, {
|
||
size: BDFDB.LibraryComponents.ModalComponents.ModalSize.DYNAMIC,
|
||
"aria-label": BDFDB.LanguageUtils.LanguageStrings.IMAGE,
|
||
children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ImageModal, {
|
||
animated: false,
|
||
src: url,
|
||
original: url,
|
||
width: this.width,
|
||
height: this.height,
|
||
className: BDFDB.disCN.imagemodalimage,
|
||
shouldAnimate: true,
|
||
renderLinkComponent: props => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Anchor, props)
|
||
})
|
||
}), true));
|
||
});
|
||
img.src = url;
|
||
}
|
||
}),
|
||
this.props.data.new && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.TextBadge, {
|
||
className: BDFDB.disCN.discoverycardcoverbadge,
|
||
style: {
|
||
borderRadius: 3,
|
||
textTransform: "uppercase",
|
||
background: BDFDB.DiscordConstants.Colors.YELLOW
|
||
},
|
||
text: BDFDB.LanguageUtils.LanguageStrings.NEW
|
||
})
|
||
]
|
||
}),
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardiconwrapper,
|
||
children: BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardicon,
|
||
children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
|
||
nativeClass: true,
|
||
iconSVG: `<svg width="100%" height="100%" viewBox="0 0 24 24"><path fill="currentColor" d="m 14.69524,1.9999881 c -0.17256,0 -0.34519,0.065 -0.47686,0.1969 L 8.8655531,7.5498683 16.449675,15.134198 21.802502,9.7812182 c 0.26333,-0.2633 0.26333,-0.6904 0,-0.9537 L 20.7902,7.8168183 c -0.22885,-0.2289 -0.58842,-0.2633 -0.85606,-0.081 l -2.127134,1.4452499 1.437076,-2.1418399 c 0.17949,-0.2675 0.14486,-0.6251001 -0.083,-0.8528001 l -2.195488,-2.19433 c -0.20264,-0.2026 -0.51169,-0.2562 -0.7698,-0.1318 l -0.37921,0.1839 0.18228,-0.4036001 c 0.11521,-0.2555 0.0599,-0.5553 -0.13834,-0.7535 l -0.68843,-0.6901 c -0.131639,-0.13172 -0.30429,-0.19701 -0.476854,-0.19701 z M 7.8695308,8.5459582 6.3201566,10.095378 c -0.126449,0.1264 -0.196927,0.298 -0.196927,0.4769 0,0.1788 0.07043,0.3505 0.196927,0.4769 l 1.469627,1.46967 c 0.283151,0.2832 0.421272,0.6744 0.377578,1.07255 -0.04365,0.3979 -0.264001,0.7495 -0.602173,0.9651 -4.3184212,2.75283 -4.720939,3.15533 -4.853187,3.28763 -0.9493352,0.9493 -0.9493352,2.494471 0,3.443871 0.9502793,0.9503 2.4954759,0.9484 3.4437772,0 0.132338,-0.1323 0.534965,-0.535 3.2875378,-4.853321 0.215049,-0.3374 0.5670574,-0.5568 0.9651044,-0.6006 0.399307,-0.044 0.790042,0.094 1.072518,0.376 l 1.469626,1.46967 c 0.26328,0.2633 0.69043,0.2633 0.95371,0 l 1.549374,-1.54942 z M 4.4762059,18.571608 c 0.243902,0 0.487705,0.092 0.673783,0.2783 0.3722,0.3722 0.3722,0.975401 0,1.347601 -0.3722,0.3722 -0.97541,0.3722 -1.3475649,0 -0.3722,-0.3722 -0.3722,-0.975401 0,-1.347601 0.1861,-0.1861 0.42988,-0.2783 0.6737819,-0.2783 z"/></svg>`
|
||
})
|
||
})
|
||
})
|
||
]
|
||
}),
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardinfo,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardtitle,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardname,
|
||
children: this.props.data.name
|
||
}),
|
||
this.props.data.latestSourceUrl &&
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, {
|
||
text: BDFDB.LanguageUtils.LanguageStrings.SCREENSHARE_SOURCE,
|
||
children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, {
|
||
className: BDFDB.disCN.discoverycardtitlebutton,
|
||
children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
|
||
nativeClass: true,
|
||
width: 16,
|
||
height: 16,
|
||
name: BDFDB.LibraryComponents.SvgIcon.Names.GITHUB
|
||
})
|
||
}),
|
||
onClick: _ => BDFDB.DiscordUtils.openLink(this.props.data.latestSourceUrl)
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FavButton, {
|
||
className: BDFDB.disCN.discoverycardtitlebutton,
|
||
isFavorite: this.props.data.fav,
|
||
onClick: value => {
|
||
this.props.data.fav = value && 1;
|
||
if (value) favorites.push(this.props.data.id);
|
||
else BDFDB.ArrayUtils.remove(favorites, this.props.data.id, true);
|
||
BDFDB.DataUtils.save(BDFDB.ArrayUtils.numSort(favorites).join(" "), _this, "favorites");
|
||
}
|
||
})
|
||
]
|
||
}),
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardauthor,
|
||
children: `by ${this.props.data.authorname}`
|
||
}),
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Scrollers.Thin, {
|
||
className: BDFDB.disCN.discoverycarddescription,
|
||
children: this.props.data.description
|
||
}),
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardfooter,
|
||
children: [
|
||
BDFDB.ArrayUtils.is(this.props.data.tags) && this.props.data.tags.length && BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardtags,
|
||
children: this.props.data.tags.map(tag => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.TextBadge, {
|
||
className: BDFDB.disCN.discoverycardtag,
|
||
style: {background: "var(--background-accent)"},
|
||
text: tag
|
||
}))
|
||
}),
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardcontrols,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardstats,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardstat,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
|
||
className: BDFDB.disCN.discoverycardstaticon,
|
||
name: BDFDB.LibraryComponents.SvgIcon.Names.DOWNLOAD
|
||
}),
|
||
this.props.data.downloads
|
||
]
|
||
}),
|
||
BDFDB.ReactUtils.createElement("div", {
|
||
className: BDFDB.disCN.discoverycardstat,
|
||
children: [
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
|
||
className: BDFDB.disCN.discoverycardstaticon,
|
||
name: BDFDB.LibraryComponents.SvgIcon.Names.HEART
|
||
}),
|
||
this.props.data.likes
|
||
]
|
||
})
|
||
]
|
||
}),
|
||
BDFDB.ReactUtils.createElement(RepoCardDownloadButtonComponent, {
|
||
...buttonData[(Object.entries(themeStates).find(n => n[1] == this.props.data.state) || [])[0]],
|
||
installed: this.props.data.state == themeStates.INSTALLED,
|
||
outdated: this.props.data.state == themeStates.OUTDATED,
|
||
onDownload: _ => {
|
||
if (!list || this.props.downloading) return;
|
||
this.props.downloading = true;
|
||
let loadingToast = BDFDB.NotificationUtils.toast(`${BDFDB.LanguageUtils.LibraryStringsFormat("loading", this.props.data.name)} - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}`, {timeout: 0, ellipsis: true});
|
||
let autoloadKey = this.props.data.state == themeStates ? "startUpdated" : "startDownloaded";
|
||
BDFDB.LibraryRequires.request(this.props.data.rawSourceUrl, (error, response, body) => {
|
||
if (error || !body) {
|
||
delete this.props.downloading;
|
||
loadingToast.close();
|
||
BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("download_fail", `Theme "${this.props.data.name}"`), {type: "danger"});
|
||
}
|
||
else list.createThemeFile(this.props.data.name, this.props.data.rawSourceUrl.split("/").pop(), body, autoloadKey).then(error2 => {
|
||
delete this.props.downloading;
|
||
loadingToast.close();
|
||
if (!error2) {
|
||
this.props.data.state = themeStates.INSTALLED;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}
|
||
});
|
||
});
|
||
},
|
||
onDelete: _ => {
|
||
if (this.props.deleting) return;
|
||
this.props.deleting = true;
|
||
BDFDB.LibraryRequires.fs.unlinkSync(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getThemesFolder(), this.props.data.rawSourceUrl.split("/").pop()));
|
||
delete this.props.deleting;
|
||
BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("delete_success", `Theme "${this.props.data.name}"`));
|
||
this.props.data.state = themeStates.DOWNLOADABLE;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}
|
||
})
|
||
]
|
||
})
|
||
]
|
||
})
|
||
]
|
||
})
|
||
]
|
||
});
|
||
}
|
||
};
|
||
|
||
const RepoCardDownloadButtonComponent = class ThemeCardDownloadButton extends BdApi.React.Component {
|
||
render() {
|
||
const backgroundColor = this.props.doDelete ? buttonData.OUTDATED.backgroundColor : this.props.doUpdate ? buttonData.INSTALLED.backgroundColor : this.props.backgroundColor;
|
||
return BDFDB.ReactUtils.createElement("button", {
|
||
className: BDFDB.disCN.discoverycardbutton,
|
||
style: {backgroundColor: BDFDB.DiscordConstants.Colors[backgroundColor] || backgroundColor},
|
||
children: [
|
||
BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
|
||
className: BDFDB.disCN.discoverycardstaticon,
|
||
width: 16,
|
||
height: 16,
|
||
name: this.props.doDelete ? BDFDB.LibraryComponents.SvgIcon.Names.TRASH : this.props.doUpdate ? BDFDB.LibraryComponents.SvgIcon.Names.DOWNLOAD : BDFDB.LibraryComponents.SvgIcon.Names[this.props.icon]
|
||
}),
|
||
this.props.doDelete ? BDFDB.LanguageUtils.LanguageStrings.APPLICATION_CONTEXT_MENU_UNINSTALL : this.props.doUpdate ? BDFDB.LanguageUtils.LanguageStrings.GAME_ACTION_BUTTON_UPDATE : (BDFDB.LanguageUtils.LibraryStringsCheck[this.props.text] ? BDFDB.LanguageUtils.LibraryStrings[this.props.text] : BDFDB.LanguageUtils.LanguageStrings[this.props.text])
|
||
],
|
||
onClick: _ => {
|
||
if (this.props.doDelete) typeof this.props.onDelete == "function" && this.props.onDelete();
|
||
else if (!this.props.installed) typeof this.props.onDownload == "function" && this.props.onDownload();
|
||
},
|
||
onMouseEnter: this.props.installed ? (_ => {
|
||
this.props.doDelete = true;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}) : this.props.outdated ? (_ => {
|
||
this.props.doUpdate = true;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}) : (_ => {}),
|
||
onMouseLeave: this.props.installed ? (_ => {
|
||
this.props.doDelete = false;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}) : this.props.outdated ? (_ => {
|
||
this.props.doUpdate = false;
|
||
BDFDB.ReactUtils.forceUpdate(this);
|
||
}) : (_ => {})
|
||
});
|
||
}
|
||
};
|
||
|
||
return class ThemeRepo extends Plugin {
|
||
onLoad () {
|
||
_this = this;
|
||
|
||
loading = {is: false, timeout: null, amount: 0};
|
||
|
||
cachedThemes = [];
|
||
grabbedThemes = [];
|
||
searchString = "";
|
||
|
||
this.defaults = {
|
||
general: {
|
||
notifyOutdated: {value: true, autoload: false, description: "Shows a Notification when one of your Themes is outdated"},
|
||
notifyNewEntries: {value: true, autoload: false, description: "Shows a Notification when there are new Entries in the Repo"},
|
||
startDownloaded: {value: false, autoload: true, description: "Starts new Themes after Download"},
|
||
startUpdated: {value: false, autoload: true, description: "Starts updated Themes after Download"}
|
||
},
|
||
filters: {
|
||
updated: {value: true, description: "Updated"},
|
||
outdated: {value: true, description: "Outdated"},
|
||
downloadable: {value: true, description: "Downloadable"},
|
||
}
|
||
};
|
||
|
||
this.modulePatches = {
|
||
before: [
|
||
"SettingsView",
|
||
"StandardSidebarView"
|
||
],
|
||
componentWillUnmount: [
|
||
"SettingsView"
|
||
]
|
||
};
|
||
}
|
||
|
||
onStart () {
|
||
generatorThemes = [];
|
||
|
||
this.forceUpdateAll();
|
||
|
||
this.loadThemes();
|
||
|
||
updateInterval = BDFDB.TimeUtils.interval(_ => this.checkForNewThemes(), 1000*60*30);
|
||
}
|
||
|
||
onStop () {
|
||
BDFDB.TimeUtils.clear(updateInterval);
|
||
BDFDB.TimeUtils.clear(loading.timeout);
|
||
|
||
this.forceUpdateAll();
|
||
|
||
BDFDB.DOMUtils.remove(BDFDB.dotCN._themereponotice, BDFDB.dotCN._themerepoloadingicon);
|
||
}
|
||
|
||
onSettingsClosed () {
|
||
if (this.SettingsUpdated) {
|
||
delete this.SettingsUpdated;
|
||
this.forceUpdateAll();
|
||
}
|
||
}
|
||
|
||
forceUpdateAll () {
|
||
favorites = BDFDB.DataUtils.load(this, "favorites");
|
||
favorites = (typeof favorites == "string" ? favorites.split(" ") : []).map(n => parseInt(n)).filter(n => !isNaN(n));
|
||
|
||
BDFDB.PatchUtils.forceAllUpdates(this);
|
||
}
|
||
|
||
onUserSettingsCogContextMenu (e) {
|
||
BDFDB.TimeUtils.timeout(_ => {
|
||
let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {props: [["label", ["BandagedBD", "BetterDiscord"]]]});
|
||
if (index > -1 && BDFDB.ArrayUtils.is(children[index].props.children)) children[index].props.children.push(BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
|
||
label: "Theme Repo",
|
||
id: BDFDB.ContextMenuUtils.createItemId(this.name, "repo"),
|
||
action: _ => BDFDB.LibraryModules.UserSettingsUtils.open("themerepo")
|
||
}));
|
||
});
|
||
}
|
||
|
||
processSettingsView (e) {
|
||
if (e.node) searchString = "";
|
||
else if (e.component.prototype && !BDFDB.PatchUtils.isPatched(this, e.component.prototype, "getPredicateSections")) {
|
||
BDFDB.PatchUtils.patch(this, e.component.prototype, "getPredicateSections", {after: e2 => {
|
||
if (BDFDB.ArrayUtils.is(e2.returnValue) && e2.returnValue.findIndex(n => n.section && (n.section.toLowerCase() == "changelog" || n.section == BDFDB.DiscordConstants.UserSettingsSections.CHANGE_LOG || n.section.toLowerCase() == "logout" || n.section == BDFDB.DiscordConstants.UserSettingsSections.LOGOUT))) {
|
||
e2.returnValue = e2.returnValue.filter(n => n.section != "themerepo");
|
||
let index = e2.returnValue.indexOf(e2.returnValue.find(n => n.section == "pluginrepo") || e2.returnValue.find(n => n.section == "themes") || e2.returnValue.find(n => n.section == BDFDB.DiscordConstants.UserSettingsSections.DEVELOPER_OPTIONS) || e2.returnValue.find(n => n.section == BDFDB.DiscordConstants.UserSettingsSections.HYPESQUAD_ONLINE));
|
||
if (index > -1) {
|
||
e2.returnValue.splice(index + 1, 0, {
|
||
className: "themerepo-tab",
|
||
section: "themerepo",
|
||
label: "Theme Repo",
|
||
element: _ => {
|
||
let options = Object.assign({}, this.settings.filters);
|
||
options.updated = options.updated && !showOnlyOutdated;
|
||
options.outdated = options.outdated || showOnlyOutdated;
|
||
options.downloadable = options.downloadable && !showOnlyOutdated;
|
||
options.sortKey = forcedSort || Object.keys(sortKeys)[0];
|
||
options.orderKey = forcedOrder || Object.keys(orderKeys)[0];
|
||
options.useLightMode = BDFDB.DiscordUtils.getTheme() == BDFDB.disCN.themelight;
|
||
options.useThemeFixer = false;
|
||
options.useCustomCSS = false;
|
||
|
||
return BDFDB.ReactUtils.createElement(RepoListComponent, options);
|
||
}
|
||
});
|
||
if (!e2.returnValue.find(n => n.section == "plugins" || n.section == "pluginrepo")) e2.returnValue.splice(index + 1, 0, {section: "DIVIDER"});
|
||
}
|
||
}
|
||
}});
|
||
}
|
||
}
|
||
|
||
processStandardSidebarView (e) {
|
||
if (e.instance.props && e.instance.props.section == "themerepo") e.instance.props.contentType = "custom";
|
||
}
|
||
|
||
generateTheme (fullCSS, generatorValues) {
|
||
if (!fullCSS || !BDFDB.ObjectUtils.is(generatorValues)) return "";
|
||
for (let inputId in generatorValues) if (generatorValues[inputId].value && generatorValues[inputId].value.trim() && generatorValues[inputId].value != generatorValues[inputId].oldValue) fullCSS = fullCSS.replace(new RegExp(`--${BDFDB.StringUtils.regEscape(inputId)}(\\s*):(\\s*)${BDFDB.StringUtils.regEscape(generatorValues[inputId].oldValue)}`,"g"),`--${inputId}$1: $2${generatorValues[inputId].value}`);
|
||
return fullCSS;
|
||
}
|
||
|
||
loadThemes (forceBanner) {
|
||
BDFDB.DOMUtils.remove(BDFDB.dotCN._themerepoloadingicon);
|
||
cachedThemes = BDFDB.DataUtils.load(this, "cached");
|
||
cachedThemes = (typeof cachedThemes == "string" ? cachedThemes.split(" ") : []).map(n => parseInt(n)).filter(n => !isNaN(n));
|
||
|
||
let loadingIcon;
|
||
let newEntries = 0, outdatedEntries = 0, checkIndex = 0, checksRunning = 0, callbackCalled = false;
|
||
|
||
const checkTheme = _ => {
|
||
if (checksRunning > 20) return;
|
||
else if (grabbedThemes.every(t => t.loaded || (!t.latestSourceUrl && !t.latest_source_url)) || !this.started || !loading.is) {
|
||
if (!callbackCalled) {
|
||
callbackCalled = true;
|
||
if (!this.started) return BDFDB.TimeUtils.clear(loading.timeout);
|
||
BDFDB.TimeUtils.clear(loading.timeout);
|
||
BDFDB.DOMUtils.remove(loadingIcon, BDFDB.dotCN._themerepoloadingicon);
|
||
loading = {is: false, timeout: null, amount: loading.amount};
|
||
|
||
BDFDB.LogUtils.log("Finished fetching Themes", this);
|
||
BDFDB.ReactUtils.forceUpdate(list);
|
||
|
||
if ((this.settings.general.notifyOutdated || forceBanner) && outdatedEntries > 0) {
|
||
let notice = document.querySelector(BDFDB.dotCN._themerepooutdatednotice);
|
||
if (notice) notice.close();
|
||
BDFDB.NotificationUtils.notice(this.labels.notice_outdated_themes.replace("{{var0}}", outdatedEntries), {
|
||
type: "danger",
|
||
className: BDFDB.disCNS._themereponotice + BDFDB.disCN._themerepooutdatednotice,
|
||
customIcon: themeRepoIcon.replace(/COLOR_[0-9]+/gi, "currentColor"),
|
||
buttons: [{
|
||
contents: BDFDB.LanguageUtils.LanguageStrings.OPEN,
|
||
close: true,
|
||
onClick: _ => {
|
||
showOnlyOutdated = true;
|
||
BDFDB.LibraryModules.UserSettingsUtils.open("themerepo");
|
||
}
|
||
}]
|
||
});
|
||
}
|
||
|
||
if (this.settings.general.notifyNewEntries && newEntries > 0) {
|
||
let notice = document.querySelector(BDFDB.dotCN._themereponewentriesnotice);
|
||
if (notice) notice.close();
|
||
BDFDB.NotificationUtils.notice(this.labels.notice_new_themes.replace("{{var0}}", newEntries), {
|
||
type: "success",
|
||
className: BDFDB.disCNS._themereponotice + BDFDB.disCN._themereponewentriesnotice,
|
||
customIcon: themeRepoIcon.replace(/COLOR_[0-9]+/gi, "currentColor"),
|
||
buttons: [{
|
||
contents: BDFDB.LanguageUtils.LanguageStrings.OPEN,
|
||
close: true,
|
||
onClick: _ => {
|
||
forcedSort = "RELEASEDATE";
|
||
forcedOrder = "ASC";
|
||
BDFDB.LibraryModules.UserSettingsUtils.open("themerepo");
|
||
}
|
||
}]
|
||
});
|
||
}
|
||
|
||
BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/_res/GeneratorList.txt", (error, response, body) => {
|
||
if (!error && body) for (let id of body.replace(/[\r\t]/g, "").split(" ").map(n => parseInt(n)).filter(n => n != null)) {
|
||
let theme = grabbedThemes.find(t => t.id == id);
|
||
if (theme) generatorThemes.push(theme);
|
||
}
|
||
});
|
||
|
||
BDFDB.LibraryRequires.request(document.querySelector("head link[rel='stylesheet'][href*='assets/app.'], body link[rel='stylesheet'][href*='assets/app.']").href, (error, response, body) => {
|
||
let nativeCSS = !error && body;
|
||
if (nativeCSS) {
|
||
let theme = BDFDB.DiscordUtils.getTheme();
|
||
let vars = (nativeCSS.split(`.${theme}{`)[1] || "").split("}")[0];
|
||
nativeCSSvars = vars ? `.theme-dark, .theme-light {${vars}}` : "";
|
||
}
|
||
else nativeCSS = nativeCSSvars = "";
|
||
});
|
||
}
|
||
return;
|
||
}
|
||
else if (checkIndex > grabbedThemes.length) return;
|
||
|
||
const theme = grabbedThemes[checkIndex++];
|
||
if (!theme || (!theme.latestSourceUrl && !theme.latest_source_url)) checkTheme();
|
||
else {
|
||
checksRunning++;
|
||
theme.releasedate = new Date(theme.releaseDate || theme.release_date || 0).getTime();
|
||
theme.latestSourceUrl = theme.latestSourceUrl || theme.latest_source_url;
|
||
theme.rawSourceUrl = theme.latestSourceUrl.replace("https://github.com/", "https://raw.githubusercontent.com/").replace(/\/blob\/(.{32,})/i, "/$1");
|
||
theme.thumbnailUrl = theme.thumbnailUrl || theme.thumbnail_url;
|
||
theme.thumbnailUrl = theme.thumbnailUrl ? (theme.thumbnailUrl.startsWith("https://") ? theme.thumbnailUrl : `https://betterdiscord.app${theme.thumbnailUrl}`) : "";
|
||
delete theme.release_date;
|
||
delete theme.latest_source_url;
|
||
delete theme.thumbnail_url;
|
||
BDFDB.LibraryRequires.request(theme.rawSourceUrl, (error, response, body) => {
|
||
if (error || !body || body.indexOf("404: Not Found") == 0) theme.failed = true;
|
||
else {
|
||
const META = body.split("*/")[0];
|
||
theme.name = BDFDB.StringUtils.upperCaseFirstChar((/@name\s+([^\t^\r^\n]+)|\/\/\**META.*["']name["']\s*:\s*["'](.+?)["']/i.exec(META) || []).filter(n => n)[1] || theme.name || "");
|
||
theme.authorname = (/@author\s+(.+)|\/\/\**META.*["']author["']\s*:\s*["'](.+?)["']/i.exec(META) || []).filter(n => n)[1] || theme.author.display_name || theme.author;
|
||
const version = (/@version\s+(.+)|\/\/\**META.*["']version["']\s*:\s*["'](.+?)["']/i.exec(META) || []).filter(n => n)[1];
|
||
if (version) theme.version = version;
|
||
if (theme.version) {
|
||
const installedTheme = this.getInstalledTheme(theme);
|
||
if (installedTheme && this.compareVersions(version, this.getString(installedTheme.version))) outdatedEntries++;
|
||
}
|
||
let text = body.trim();
|
||
let hasMETAline = text.replace(/\s/g, "").indexOf("//META{"), newMeta = "";
|
||
if (hasMETAline < 20 && hasMETAline > -1) {
|
||
let i = 0, j = 0, metaString = "";
|
||
try {
|
||
for (let c of `{${text.split("{").slice(1).join("{")}`) {
|
||
metaString += c;
|
||
if (c == "{") i++;
|
||
else if (c == "}") j++;
|
||
if (i > 0 && i == j) break;
|
||
}
|
||
let metaObj = JSON.parse(metaString);
|
||
newMeta = "/**\n";
|
||
for (let key in metaObj) newMeta += ` * @${key} ${metaObj[key]}\n`;
|
||
newMeta += "*/";
|
||
}
|
||
catch (err) {newMeta = "";}
|
||
}
|
||
theme.fullCSS = [newMeta, newMeta ? text.split("\n").slice(1).join("\n") : text].filter(n => n).join("\n");
|
||
theme.css = (hasMETAline < 20 && hasMETAline > -1 ? text.split("\n").slice(1).join("\n") : text).replace(/[\r|\n|\t]/g, "");
|
||
if (!cachedThemes.includes(theme.id)) newEntries++;
|
||
}
|
||
|
||
theme.loaded = true;
|
||
|
||
let loadingTooltip = document.querySelector(BDFDB.dotCN._themerepoloadingtooltip);
|
||
if (loadingTooltip) loadingTooltip.update(this.getLoadingTooltipText());
|
||
|
||
checksRunning--;
|
||
checkTheme();
|
||
});
|
||
}
|
||
};
|
||
|
||
BDFDB.TimeUtils.timeout(_ => BDFDB.LibraryRequires.request("https://api.betterdiscord.app/v1/store/themes", {bdVersion: true}, (error, response, body) => {
|
||
if (!error && body && response.statusCode == 200) try {
|
||
grabbedThemes = BDFDB.ArrayUtils.keySort(JSON.parse(body).filter(n => n), "name");
|
||
|
||
BDFDB.DataUtils.save(BDFDB.ArrayUtils.numSort(grabbedThemes.map(n => n.id)).join(" "), this, "cached");
|
||
|
||
loading = {is: true, timeout: BDFDB.TimeUtils.timeout(_ => {
|
||
BDFDB.TimeUtils.clear(loading.timeout);
|
||
if (this.started) {
|
||
if (loading.is && loading.amount < 4) BDFDB.TimeUtils.timeout(_ => this.loadThemes(), 10000);
|
||
loading = {is: false, timeout: null, amount: loading.amount};
|
||
}
|
||
}, 1200000), amount: loading.amount + 1};
|
||
|
||
loadingIcon = BDFDB.DOMUtils.create(themeRepoIcon.replace(/COLOR_1/gi, "var(--bdfdb-blurple)").replace(/COLOR_2/gi, "#72767d"));
|
||
BDFDB.DOMUtils.addClass(loadingIcon, BDFDB.disCN._themerepoloadingicon);
|
||
loadingIcon.addEventListener("mouseenter", _ => {
|
||
BDFDB.TooltipUtils.create(loadingIcon, this.getLoadingTooltipText(), {
|
||
type: "left",
|
||
className: BDFDB.disCN._themerepoloadingtooltip,
|
||
delay: 500,
|
||
style: "max-width: unset;"
|
||
});
|
||
});
|
||
BDFDB.PluginUtils.addLoadingIcon(loadingIcon);
|
||
|
||
BDFDB.ReactUtils.forceUpdate(list);
|
||
|
||
for (let i = 0; i <= 20; i++) checkTheme();
|
||
}
|
||
catch (err) {BDFDB.NotificationUtils.toast("Failed to load Theme Store", {type: "danger"});}
|
||
let status = {};
|
||
if (response && response.statusCode == 403) status = {code: response.statusCode, reason: " due to DDoS Protection"};
|
||
else if (response && response.statusCode == 404) status = {code: response.statusCode, reason: " due to DDoS Protection"};
|
||
else if (response && response.statusCode == 502) status = {code: response.statusCode, reason: ", because the API is down"};
|
||
if (status.code) {
|
||
BDFDB.NotificationUtils.toast("Failed to fetch Theme Store from the Website API" + status.reason, {type: "danger"});
|
||
errorState = status.code;
|
||
}
|
||
else errorState = null;
|
||
}), 10000);
|
||
}
|
||
|
||
getLoadingTooltipText () {
|
||
return BDFDB.LanguageUtils.LibraryStringsFormat("loading", `Theme Repo - [${grabbedThemes.filter(n => n.loaded).length}/${grabbedThemes.length}]`);
|
||
}
|
||
|
||
getString (obj) {
|
||
let string = "";
|
||
if (typeof obj == "string") string = obj;
|
||
else if (obj && obj.props) {
|
||
if (typeof obj.props.children == "string") string = obj.props.children;
|
||
else if (Array.isArray(obj.props.children)) for (let c of obj.props.children) string += typeof c == "string" ? c : this.getString(c);
|
||
}
|
||
return string;
|
||
}
|
||
|
||
compareVersions (v1, v2) {
|
||
return !(v1 == v2 || !BDFDB.NumberUtils.compareVersions(v1, v2));
|
||
}
|
||
|
||
getInstalledTheme (theme) {
|
||
if (!theme || typeof theme.authorname != "string") return;
|
||
const iTheme = BDFDB.BDUtils.getTheme(theme.name, false, true);
|
||
if (iTheme && (theme.authorname.toUpperCase().indexOf(this.getString(iTheme.author).toUpperCase()) > -1 || this.getString(iTheme.author).toUpperCase().indexOf(theme.authorname.toUpperCase()) > -1)) return iTheme;
|
||
else if (theme.rawSourceUrl && window.BdApi && BdApi.Themes && typeof BdApi.Themes.getAll == "function") {
|
||
const filename = theme.rawSourceUrl.split("/").pop();
|
||
for (let t of BdApi.Themes.getAll()) if (t.filename == filename && (theme.authorname.toUpperCase().indexOf(this.getString(t.author).toUpperCase()) > -1 || this.getString(t.author).toUpperCase().indexOf(theme.authorname.toUpperCase()) > -1)) return t;
|
||
}
|
||
}
|
||
|
||
checkForNewThemes () {
|
||
BDFDB.LibraryRequires.request("https://api.betterdiscord.app/v1/store/themes", {bdVersion: true}, (error, response, body) => {
|
||
if (!error && body) try {
|
||
if (JSON.parse(body).filter(n => n).length != grabbedThemes.length) {
|
||
loading = {is: false, timeout: null, amount: 0};
|
||
this.loadThemes();
|
||
}
|
||
}
|
||
catch (err) {BDFDB.NotificationUtils.toast("Failed to load Theme Store", {type: "danger"});}
|
||
});
|
||
}
|
||
|
||
setLabelsByLanguage () {
|
||
switch (BDFDB.LanguageUtils.getLanguage().id) {
|
||
case "bg": // Bulgarian
|
||
return {
|
||
list: "Списък",
|
||
notice_failed_themes: "Някои Themes [{{var0}}] не можаха да бъдат заредени",
|
||
notice_new_themes: "Новите Themes [{{var0}}] бяха добавени към Theme Repo",
|
||
notice_outdated_themes: "Някои Themes [{{var0}}] са остарели"
|
||
};
|
||
case "da": // Danish
|
||
return {
|
||
list: "Liste",
|
||
notice_failed_themes: "Nogle Themes [{{var0}}] kunne ikke indlæses",
|
||
notice_new_themes: "Nye Themes [{{var0}}] er blevet føjet til Theme Repo",
|
||
notice_outdated_themes: "Nogle Themes [{{var0}}] er forældede"
|
||
};
|
||
case "de": // German
|
||
return {
|
||
list: "Liste",
|
||
notice_failed_themes: "Einige Themes [{{var0}}] konnten nicht geladen werden",
|
||
notice_new_themes: "Neue Themes [{{var0}}] wurden zur Theme Repo hinzugefügt",
|
||
notice_outdated_themes: "Einige Themes [{{var0}}] sind veraltet"
|
||
};
|
||
case "el": // Greek
|
||
return {
|
||
list: "Λίστα",
|
||
notice_failed_themes: "Ορισμένα Θέματα [{{var0}}] δεν μπορούν να φορτωθούν",
|
||
notice_new_themes: "Νέα Θέματα [{{var0}}] προστέθηκαν στο Απωθετήριο Θεμάτων",
|
||
notice_outdated_themes: "Ορισμένα Θέματα [{{var0}}] είναι παλαιά"
|
||
};
|
||
case "es": // Spanish
|
||
return {
|
||
list: "Lista",
|
||
notice_failed_themes: "Algunos Themes [{{var0}}] no se pudieron cargar",
|
||
notice_new_themes: "Se han agregado nuevos Themes [{{var0}}] a Theme Repo",
|
||
notice_outdated_themes: "Algunas Themes [{{var0}}] están desactualizadas"
|
||
};
|
||
case "fi": // Finnish
|
||
return {
|
||
list: "Lista",
|
||
notice_failed_themes: "Joitain kohdetta Themes [{{var0}}] ei voitu ladata",
|
||
notice_new_themes: "Uusi Themes [{{var0}}] on lisätty Theme Repo",
|
||
notice_outdated_themes: "Jotkut Themes [{{var0}}] ovat vanhentuneita"
|
||
};
|
||
case "fr": // French
|
||
return {
|
||
list: "Liste",
|
||
notice_failed_themes: "Certains Themes [{{var0}}] n'ont pas pu être chargés",
|
||
notice_new_themes: "De nouveaux Themes [{{var0}}] ont été ajoutés à Theme Repo",
|
||
notice_outdated_themes: "Certains Themes [{{var0}}] sont obsolètes"
|
||
};
|
||
case "hr": // Croatian
|
||
return {
|
||
list: "Popis",
|
||
notice_failed_themes: "Neke datoteke Themes [{{var0}}] nije moguće učitati",
|
||
notice_new_themes: "Novi Themes [{{var0}}] dodani su u Theme Repo",
|
||
notice_outdated_themes: "Neki su Themes [{{var0}}] zastarjeli"
|
||
};
|
||
case "hu": // Hungarian
|
||
return {
|
||
list: "Lista",
|
||
notice_failed_themes: "Néhány Themes [{{var0}}] nem sikerült betölteni",
|
||
notice_new_themes: "Új Themes [{{var0}}] hozzáadva a következőhöz: Theme Repo",
|
||
notice_outdated_themes: "Néhány Themes [{{var0}}] elavult"
|
||
};
|
||
case "it": // Italian
|
||
return {
|
||
list: "Elenco",
|
||
notice_failed_themes: "Impossibile caricare alcuni Themes [{{var0}}] ",
|
||
notice_new_themes: "Il nuovo Themes [{{var0}}] è stato aggiunto a Theme Repo",
|
||
notice_outdated_themes: "Alcuni Themes [{{var0}}] non sono aggiornati"
|
||
};
|
||
case "ja": // Japanese
|
||
return {
|
||
list: "リスト",
|
||
notice_failed_themes: "一部の Themes [{{var0}}] を読み込めませんでした",
|
||
notice_new_themes: "新しい Themes [{{var0}}] が Theme Repo に追加されました",
|
||
notice_outdated_themes: "一部の Themes [{{var0}}] は古くなっています"
|
||
};
|
||
case "ko": // Korean
|
||
return {
|
||
list: "명부",
|
||
notice_failed_themes: "일부 Themes [{{var0}}] 을 (를)로드 할 수 없습니다.",
|
||
notice_new_themes: "새 Themes [{{var0}}] 이 Theme Repo 에 추가되었습니다.",
|
||
notice_outdated_themes: "일부 Themes [{{var0}}] 이 오래되었습니다."
|
||
};
|
||
case "lt": // Lithuanian
|
||
return {
|
||
list: "Sąrašas",
|
||
notice_failed_themes: "Kai kurių Themes [{{var0}}] nepavyko įkelti",
|
||
notice_new_themes: "Naujas Themes [{{var0}}] pridėtas prie Theme Repo",
|
||
notice_outdated_themes: "Kai kurie Themes [{{var0}}] yra pasenę"
|
||
};
|
||
case "nl": // Dutch
|
||
return {
|
||
list: "Lijst",
|
||
notice_failed_themes: "Sommige Themes [{{var0}}] konden niet worden geladen",
|
||
notice_new_themes: "Nieuwe Themes [{{var0}}] zijn toegevoegd aan de Theme Repo",
|
||
notice_outdated_themes: "Sommige Themes [{{var0}}] zijn verouderd"
|
||
};
|
||
case "no": // Norwegian
|
||
return {
|
||
list: "Liste",
|
||
notice_failed_themes: "Noen Themes [{{var0}}] kunne ikke lastes inn",
|
||
notice_new_themes: "Nye Themes [{{var0}}] er lagt til i Theme Repo",
|
||
notice_outdated_themes: "Noen Themes [{{var0}}] er utdaterte"
|
||
};
|
||
case "pl": // Polish
|
||
return {
|
||
list: "Lista",
|
||
notice_failed_themes: "Nie można załadować niektórych Themes [{{var0}}] ",
|
||
notice_new_themes: "Nowe Themes [{{var0}}] zostały dodane do Theme Repo",
|
||
notice_outdated_themes: "Niektóre Themes [{{var0}}] są nieaktualne"
|
||
};
|
||
case "pt-BR": // Portuguese (Brazil)
|
||
return {
|
||
list: "Lista",
|
||
notice_failed_themes: "Algum Themes [{{var0}}] não pôde ser carregado",
|
||
notice_new_themes: "Novo Themes [{{var0}}] foi adicionado ao Theme Repo",
|
||
notice_outdated_themes: "Alguns Themes [{{var0}}] estão desatualizados"
|
||
};
|
||
case "ro": // Romanian
|
||
return {
|
||
list: "Listă",
|
||
notice_failed_themes: "Unele Themes [{{var0}}] nu au putut fi încărcate",
|
||
notice_new_themes: "Themes [{{var0}}] nou au fost adăugate la Theme Repo",
|
||
notice_outdated_themes: "Unele Themes [{{var0}}] sunt învechite"
|
||
};
|
||
case "ru": // Russian
|
||
return {
|
||
list: "Список",
|
||
notice_failed_themes: "Не удалось загрузить некоторые Themes [{{var0}}] ",
|
||
notice_new_themes: "Новые Themes [{{var0}}] добавлены в Theme Repo",
|
||
notice_outdated_themes: "Некоторые Themes [{{var0}}] устарели"
|
||
};
|
||
case "sv": // Swedish
|
||
return {
|
||
list: "Lista",
|
||
notice_failed_themes: "Vissa Themes [{{var0}}] kunde inte laddas",
|
||
notice_new_themes: "Nya Themes [{{var0}}] har lagts till i Theme Repo",
|
||
notice_outdated_themes: "Vissa Themes [{{var0}}] är föråldrade"
|
||
};
|
||
case "th": // Thai
|
||
return {
|
||
list: "รายการ",
|
||
notice_failed_themes: "ไม่สามารถโหลด Themes [{{var0}}] บางรายการได้",
|
||
notice_new_themes: "เพิ่ม Themes [{{var0}}] ใหม่ใน Theme Repo แล้ว",
|
||
notice_outdated_themes: "Themes [{{var0}}] บางรายการล้าสมัย"
|
||
};
|
||
case "tr": // Turkish
|
||
return {
|
||
list: "Liste",
|
||
notice_failed_themes: "Bazı Themes [{{var0}}] yüklenemedi",
|
||
notice_new_themes: "Yeni Themes [{{var0}}], Theme Repo 'ye eklendi",
|
||
notice_outdated_themes: "Bazı Themes [{{var0}}] güncel değil"
|
||
};
|
||
case "uk": // Ukrainian
|
||
return {
|
||
list: "Список",
|
||
notice_failed_themes: "Деякі Themes [{{var0}}] не вдалося завантажити",
|
||
notice_new_themes: "Нові Themes [{{var0}}] були додані до Theme Repo",
|
||
notice_outdated_themes: "Деякі Themes [{{var0}}] застарілі"
|
||
};
|
||
case "vi": // Vietnamese
|
||
return {
|
||
list: "Danh sách",
|
||
notice_failed_themes: "Không thể tải một số Themes [{{var0}}] ",
|
||
notice_new_themes: "Themes [{{var0}}] mới đã được thêm vào Theme Repo",
|
||
notice_outdated_themes: "Một số Themes [{{var0}}] đã lỗi thời"
|
||
};
|
||
case "zh-CN": // Chinese (China)
|
||
return {
|
||
list: "清单",
|
||
notice_failed_themes: "某些 Themes [{{var0}}] 无法加载",
|
||
notice_new_themes: "新的 Themes [{{var0}}] 已添加到 Theme Repo",
|
||
notice_outdated_themes: "一些 Themes [{{var0}}] 已过时"
|
||
};
|
||
case "zh-TW": // Chinese (Taiwan)
|
||
return {
|
||
list: "清單",
|
||
notice_failed_themes: "某些 Themes [{{var0}}] 無法加載",
|
||
notice_new_themes: "新的 Themes [{{var0}}] 已添加到 Theme Repo",
|
||
notice_outdated_themes: "一些 Themes [{{var0}}] 已過時"
|
||
};
|
||
default: // English
|
||
return {
|
||
list: "List",
|
||
notice_failed_themes: "Some Themes [{{var0}}] could not be loaded",
|
||
notice_new_themes: "New Themes [{{var0}}] have been added to the Theme Repo",
|
||
notice_outdated_themes: "Some Themes [{{var0}}] are outdated"
|
||
};
|
||
}
|
||
}
|
||
};
|
||
})(window.BDFDB_Global.PluginUtils.buildPlugin(changeLog));
|
||
})(); |