diff --git a/assets/locales/en-us.json b/assets/locales/en-us.json index 71a5d6d3..c9968fc7 100644 --- a/assets/locales/en-us.json +++ b/assets/locales/en-us.json @@ -1,5 +1,6 @@ { "Panels": { + "updates": "Updates", "plugins": "Plugins", "themes": "Themes", "customcss": "Custom CSS" @@ -237,6 +238,7 @@ "metaNotFound": "META was not found.", "compileError": "Could not be compiled. See console for details.", "wasUnloaded": "{{name}} was unloaded.", + "wasLoaded": "{{name}} v{{version}} was loaded.", "blankSlateHeader": "You don't have any {{type}}s!", "blankSlateMessage": "Grab some from [this website]({{link}}) and add them to your {{type}} folder." }, @@ -258,7 +260,7 @@ "failureMessage": "BetterDiscord failed to download emotes, please check your internet connection and firewall." }, "PublicServers": { - "button": "public", + "button": "Public Servers", "join": "Join", "joining": "Joining", "joined": "Joined", @@ -295,6 +297,9 @@ "additionalInfo": "Additional Info", "restartPrompt": "In order to take effect, Discord needs to be restarted. Do you want to restart now?" }, + "Notices": { + "moreInfo": "More Info" + }, "ReactDevTools": { "notFound": "Extension Not Found", "notFoundDetails": "Unable to find the React Developer Tools extension on your PC. Please install the extension on your local Chrome installation." @@ -305,17 +310,21 @@ "ascending": "Ascending", "descending": "Descending" }, - "Startup": { - "notSupported": "Not Supported", - "incompatibleApp": "BetterDiscord does not work with {{app}}. Please uninstall one of them.", - "updateNow": "Update Now", - "maybeLater": "Maybe Later", - "updateAvailable": "Update Available", - "updateInfo": "There is an update available for BetterDiscord's Injector ({{version}}).\n\nYou can either update and restart now, or later.", - "updateFailed": "Could Not Update", - "manualUpdate": "Unable to update automatically, please download the installer and reinstall normally.\n\n[Download Installer](https://github.com/rauenzi/BetterDiscordApp/releases/latest)", - "jqueryFailed": "jQuery Failed To Load", - "jqueryFailedDetails": "jQuery could not be loaded, and some plugins may not work properly. Proceed at your own risk." + "Updater": { + "updateFailed": "Update Failed!", + "updateFailedMessage": "BetterDiscord failed to update. Please download the latest version of the installer from our website (https://betterdiscord.app/) and reinstall.", + "updateSuccessful": "Update Successful!", + "updateAvailable": "BetterDiscord has a new update (v{{version}})", + "addonUpdatesAvailable": "BetterDiscord has found updates for {{count}} of your {{.type}}s!", + "addonUpdated": "{{name}} has been updated to version {{version}}!", + "checking": "Checking for updates!", + "finishedChecking": "Finished checking for updates!", + "checkForUpdates": "Check For Updates!", + "updateAll": "Update All!", + "noUpdatesAvailable": "No updates available.", + "versionAvailable": "Version {{version}} now available!", + "upToDateBlankslate": "All of your {{type}} seem to be up to date!", + "updateButton": "Update!" }, "WindowPrefs": { "enabledInfo": "This option requires a transparent theme in order to work properly. On Windows this may break your aero snapping and maximizing.\n\nIn order to take effect, Discord needs to be restarted. Do you want to restart now?", diff --git a/preload/src/api/https.js b/preload/src/api/https.js index 2cc2a0cf..dae2ac0f 100644 --- a/preload/src/api/https.js +++ b/preload/src/api/https.js @@ -35,10 +35,10 @@ const makeRequest = (url, options, callback, setReq) => { }); }); req.end(); -} +}; const request = function (url, options, callback) { - let responseObject = undefined; + let responseObject = null; let reqObject = null; let pipe = null; @@ -56,7 +56,8 @@ const request = function (url, options, callback) { pipe(fsStream) { if (!responseObject) { pipe = fsStream; - } else { + } + else { responseObject.pipe(fsStream); } } diff --git a/preload/src/patcher.js b/preload/src/patcher.js index 1117adfa..d01c7f52 100644 --- a/preload/src/patcher.js +++ b/preload/src/patcher.js @@ -1,5 +1,7 @@ import {webFrame} from "electron"; +/* global window:false */ + export default function () { const patcher = function () { const chunkName = "webpackChunkdiscord_app"; @@ -7,21 +9,24 @@ export default function () { const value = target[prop]; Object.defineProperty(target, prop, { get() {return value;}, - set(value) { + set(newValue) { Object.defineProperty(target, prop, { - value, + value: newValue, configurable: true, enumerable: true, writable: true }); try { - effect(value); - } catch (error) { + effect(newValue); + } + catch (error) { + // eslint-disable-next-line no-console console.error(error); } - return value; + // eslint-disable-next-line no-setter-return + return newValue; }, configurable: true }); @@ -41,7 +46,7 @@ export default function () { configurable: true }); } - } + }; }]); instance.pop(); diff --git a/renderer/src/builtins/general/publicservers.js b/renderer/src/builtins/general/publicservers.js index 7dc3dfe5..a2e48d17 100644 --- a/renderer/src/builtins/general/publicservers.js +++ b/renderer/src/builtins/general/publicservers.js @@ -60,7 +60,7 @@ export default new class PublicServers extends Builtin { { id: "public-servers-button", onClick: () => this.openPublicServers(), - text: "Public Servers", + text: Strings.PublicServers.button, icon: () => React.createElement(Globe, {color: "currentColor"}) } ) @@ -93,8 +93,10 @@ export default new class PublicServers extends Builtin { aSlot.dataset.listItemId = "public-servers"; // Remove any badges - const badge = newButton.querySelector(`[class*="premiumTrial"]`); - badge?.remove?.(); + const premiumBadge = newButton.querySelector(`[class*="premiumTrial"]`); + premiumBadge?.remove?.(); + const numberBadge = newButton.querySelector(`[class*="numberBadge-"]`); + numberBadge?.remove?.(); // Render our icon in the avatar slot const avatarSlot = newButton.querySelector(`[class*="avatar-"]`); @@ -104,15 +106,18 @@ export default new class PublicServers extends Builtin { // Replace the existing name const nameSlot = newButton.querySelector(`[class*="name-"]`); - nameSlot.textContent = "Public Servers"; + nameSlot.textContent = Strings.PublicServers.button; // Insert before the header, end of the list header.parentNode.insertBefore(newButton, header); + + this.button = newButton; } disabled() { this.unpatchAll(); - // DOM.query("#bd-pub-li").remove(); + this.button?.remove?.(); + document.querySelector("#public-servers-button")?.parentElement?.parentElement?.remove?.(); } async _appendButton() { @@ -130,12 +135,4 @@ export default new class PublicServers extends Builtin { openPublicServers() { LayerManager.pushLayer(() => DiscordModules.React.createElement(PublicServersMenu, {close: LayerManager.popLayer})); } - - get button() { - const btn = DOMManager.parseHTML(`
`); - const label = DOMManager.parseHTML(`
${Strings.PublicServers.button}
`); - label.addEventListener("click", () => {this.openPublicServers();}); - btn.append(label); - return btn; - } }; \ No newline at end of file diff --git a/renderer/src/modules/addonmanager.js b/renderer/src/modules/addonmanager.js index 245c0a76..f361f116 100644 --- a/renderer/src/modules/addonmanager.js +++ b/renderer/src/modules/addonmanager.js @@ -220,7 +220,7 @@ export default class AddonManager { return error; } - if (shouldToast) Toasts.success(`${addon.name} v${addon.version} was loaded.`); + if (shouldToast) Toasts.success(Strings.Addons.wasUnloaded.format({name: addon.name, version: addon.version})); this.emit("loaded", addon); if (!this.state[addon.id]) return this.state[addon.id] = false; @@ -235,7 +235,7 @@ export default class AddonManager { this.addonList.splice(this.addonList.indexOf(addon), 1); this.emit("unloaded", addon); - if (shouldToast) Toasts.success(`${addon.name} was unloaded.`); + if (shouldToast) Toasts.success(Strings.Addons.wasUnloaded.format({name: addon.name})); return true; } diff --git a/renderer/src/modules/api/contextmenu.js b/renderer/src/modules/api/contextmenu.js index b1ae6a06..34a8902c 100644 --- a/renderer/src/modules/api/contextmenu.js +++ b/renderer/src/modules/api/contextmenu.js @@ -30,7 +30,7 @@ const MenuComponents = (() => { const ContextMenuActions = (() => { const out = {}; - const ActionsModule = WebpackModules.getModule(m => Object.values(m).some(m => typeof m === "function" && m.toString().includes("CONTEXT_MENU_CLOSE")), {searchExports: false}); + const ActionsModule = WebpackModules.getModule(m => Object.values(m).some(v => typeof v === "function" && v.toString().includes("CONTEXT_MENU_CLOSE")), {searchExports: false}); for (const key of Object.keys(ActionsModule)) { if (ActionsModule[key].toString().includes("CONTEXT_MENU_CLOSE")) { @@ -51,10 +51,10 @@ class MenuPatcher { static initialize() { const {module, key} = (() => { - const module = WebpackModules.getModule(m => Object.values(m).some(v => typeof v === "function" && v.toString().includes("CONTEXT_MENU_CLOSE")), {searchExports: false}); - const key = Object.keys(module).find(key => module[key].length === 3); + const foundModule = WebpackModules.getModule(m => Object.values(m).some(v => typeof v === "function" && v.toString().includes("CONTEXT_MENU_CLOSE")), {searchExports: false}); + const foundKey = Object.keys(foundModule).find(k => foundModule[k].length === 3); - return {module, key}; + return {module: foundModule, key: foundKey}; })(); Patcher.before("ContextMenuPatcher", module, key, (_, methodArguments) => { diff --git a/renderer/src/modules/patcher.js b/renderer/src/modules/patcher.js index ae9beb4b..836046f3 100644 --- a/renderer/src/modules/patcher.js +++ b/renderer/src/modules/patcher.js @@ -110,7 +110,8 @@ export default class Patcher { get: () => patch.originalFunction, set: undefined }); - } else { + } + else { patch.module[patch.functionName] = patch.originalFunction; } @@ -132,9 +133,11 @@ export default class Patcher { enumerable: true, ...descriptor, get: () => patch.proxyFunction, + // eslint-disable-next-line no-setter-return set: value => (patch.originalFunction = value) }); - } else { + } + else { patch.getter = false; module[functionName] = patch.proxyFunction; } @@ -172,7 +175,7 @@ export default class Patcher { * * @callback module:Patcher~patchCallback * @param {object} thisObject - `this` in the context of the original function. - * @param {arguments} arguments - The original arguments of the original function. + * @param {args} args - The original arguments of the original function. * @param {(function|*)} extraValue - For `instead` patches, this is the original function from the module. For `after` patches, this is the return value of the function. * @return {*} Makes sense only when using an `instead` or `after` patch. If something other than `undefined` is returned, the returned value replaces the value of `returnValue`. If used for `before` the return value is ignored. */ diff --git a/renderer/src/modules/pluginmanager.js b/renderer/src/modules/pluginmanager.js index 1e7c2c65..38e74e5f 100644 --- a/renderer/src/modules/pluginmanager.js +++ b/renderer/src/modules/pluginmanager.js @@ -107,11 +107,11 @@ export default new class PluginManager extends AddonManager { } catch (error) { this.state[addon.id] = false; - return new AddonError(addon.name, addon.filename, "load() could not be fired.", {message: error.message, stack: error.stack}, this.prefix); + return new AddonError(addon.name, addon.filename, Strings.Addons.methodError.format({method: "load()"}), {message: error.message, stack: error.stack}, this.prefix); } } catch (error) { - return new AddonError(addon.name, addon.filename, "Could not be constructed.", {message: error.message, stack: error.stack}, this.prefix); + return new AddonError(addon.name, addon.filename, Strings.Addons.methodError.format({method: "Plugin constructor()"}), {message: error.message, stack: error.stack}, this.prefix); } } @@ -130,7 +130,7 @@ export default new class PluginManager extends AddonManager { return addon; } catch (err) { - throw new AddonError(addon.name || addon.filename, module.filename, "Plugin could not be compiled", {message: err.message, stack: err.stack}, this.prefix); + throw new AddonError(addon.name || addon.filename, module.filename, Strings.Addons.compileError, {message: err.message, stack: err.stack}, this.prefix); } } diff --git a/renderer/src/modules/updater.js b/renderer/src/modules/updater.js index 0b7775cb..8002574e 100644 --- a/renderer/src/modules/updater.js +++ b/renderer/src/modules/updater.js @@ -46,7 +46,7 @@ const reducer = (acc, addon) => { export default class Updater { static initialize() { - Settings.registerPanel("updates", "Updates", { + Settings.registerPanel("updates", Strings.Panels.updates, { order: 1, element: () => { return React.createElement(UpdaterPanel, { @@ -90,9 +90,9 @@ export class CoreUpdater { this.remoteVersion = remoteVersion; if (!this.hasUpdate || !showNotice) return; - const close = Notices.info(`BetterDiscord has a new update (v${remoteVersion})`, { + const close = Notices.info(Strings.Updater.updateAvailable.format({version: remoteVersion}), { buttons: [{ - label: "More Info", + label: Strings.Notices.moreInfo, onClick: () => { close(); UserSettingsWindow?.open?.("updates"); @@ -118,7 +118,7 @@ export class CoreUpdater { this.hasUpdate = false; Config.version = this.remoteVersion; - Modals.showConfirmationModal("Update Successful!", "BetterDiscord updated successfully. Discord needs to restart in order for it to take effect. Do you want to do this now?", { + Modals.showConfirmationModal(Strings.Updater.updateSuccessful, Strings.Modals.restartPrompt, { confirmText: Strings.Modals.restartNow, cancelText: Strings.Modals.restartLater, danger: true, @@ -127,7 +127,7 @@ export class CoreUpdater { } catch (err) { Logger.stacktrace("Updater", "Failed to update", err); - Modals.showConfirmationModal("Update Failed", "BetterDiscord failed to update. Please download the latest version of the installer from GitHub (https://github.com/BetterDiscord/Installer/releases/latest) and reinstall.", { + Modals.showConfirmationModal(Strings.Updater.updateFailed, Strings.Updater.updateFailedMessage, { cancelText: null }); } @@ -191,7 +191,7 @@ class AddonUpdater { const file = path.join(path.resolve(this.manager.addonFolder), filename); fileSystem.writeFile(file, body.toString(), () => { - Toasts.success(`${info.name} has been updated to version ${info.version}!`); + Toasts.success(Strings.Updater.addonUpdated.format({name: info.name, version: info.version})); this.pending.splice(this.pending.indexOf(filename), 1); }); }); @@ -199,9 +199,9 @@ class AddonUpdater { showUpdateNotice() { if (!this.pending.length) return; - const close = Notices.info(`BetterDiscord has found updates for ${this.pending.length} of your ${this.type}s!`, { + const close = Notices.info(Strings.Updater.addonUpdatesAvailable.format({count: this.pending.length, type: this.type}), { buttons: [{ - label: "More Info", + label: Strings.Notices.moreInfo, onClick: () => { close(); UserSettingsWindow?.open?.("updates"); diff --git a/renderer/src/ui/modals.js b/renderer/src/ui/modals.js index 819c7eed..3cac96da 100644 --- a/renderer/src/ui/modals.js +++ b/renderer/src/ui/modals.js @@ -83,7 +83,12 @@ export default class Modals { for (const button of buttons) { const buttonEl = Object.assign(document.createElement("button"), { onclick: (e) => { - try {button.action(e);} catch (error) {console.error(error);} + try { + button.action(e); + } + catch (error) { + Logger.stacktrace("Modals", "Could not fire button listener", error); + } handleClose(); }, @@ -91,7 +96,7 @@ export default class Modals { className: "bd-button" }); - if (button.danger) buttonEl.classList.add("bd-button-danger") + if (button.danger) buttonEl.classList.add("bd-button-danger"); buttonEl.append(button.label); buttonContainer.appendChild(buttonEl); @@ -102,14 +107,16 @@ export default class Modals { try { ReactDOM.render(content, container); - } catch (error) { + } + catch (error) { container.append(DOMManager.parseHTML(`There was an unexpected error. Modal could not be rendered.`)); } DOMManager.onRemoved(container, () => { ReactDOM.unmountComponentAtNode(container); }); - } else { + } + else { modal.querySelector(".scroller").append(content); } @@ -120,7 +127,8 @@ export default class Modals { if (this.hasModalOpen) { this.ModalQueue.push(handleOpen); - } else { + } + else { handleOpen(); } } @@ -152,15 +160,17 @@ export default class Modals { const emptyFunction = () => {}; const {onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = Strings.Modals.okay, cancelText = Strings.Modals.cancel, danger = false, key = undefined} = options; - if (!this.ModalActions || !this.ConfirmationModal || !this.Markdown) return this.default(title, content, [ - confirmText && {label: confirmText, action: onConfirm}, - cancelText && {label: cancelText, action: onCancel, danger} - ].filter(Boolean)); + if (!this.ModalActions || !this.ConfirmationModal || !this.Markdown) { + return this.default(title, content, [ + confirmText && {label: confirmText, action: onConfirm}, + cancelText && {label: cancelText, action: onCancel, danger} + ].filter(Boolean)); + } if (!Array.isArray(content)) content = [content]; content = content.map(c => typeof(c) === "string" ? React.createElement(Markdown, null, c) : c); - let modalKey = ModalActions.openModal(props => { + const modalKey = ModalActions.openModal(props => { return React.createElement(ErrorBoundary, { onError: () => { setTimeout(() => { diff --git a/renderer/src/ui/settings/group.jsx b/renderer/src/ui/settings/group.jsx index fba8c810..4911135e 100644 --- a/renderer/src/ui/settings/group.jsx +++ b/renderer/src/ui/settings/group.jsx @@ -1,7 +1,5 @@ import {React} from "modules"; import Drawer from "./drawer"; -import Title from "./title"; -import Divider from "../divider"; import Switch from "./components/switch"; import Dropdown from "./components/dropdown"; import Number from "./components/number"; diff --git a/renderer/src/ui/updater.jsx b/renderer/src/ui/updater.jsx index f26f67de..8609b7ec 100644 --- a/renderer/src/ui/updater.jsx +++ b/renderer/src/ui/updater.jsx @@ -1,5 +1,5 @@ import {Config} from "data"; -import {React, Events} from "modules"; +import {React, Events, Strings} from "modules"; import Drawer from "./settings/drawer"; import SettingItem from "./settings/components/item"; import SettingsTitle from "./settings/title"; @@ -10,9 +10,9 @@ import Checkmark from "./icons/check"; class CoreUpdaterPanel extends React.Component { render() { return - + {!this.props.hasUpdate &&
} - {this.props.hasUpdate && } + {this.props.hasUpdate && }
; } @@ -22,7 +22,7 @@ class NoUpdates extends React.Component { render() { return
- {`All of your ${this.props.type} seem to be up to date!`} + {Strings.Updater.upToDateBlankslate.format({type: this.props.type})}
; } } @@ -30,13 +30,13 @@ class NoUpdates extends React.Component { class AddonUpdaterPanel extends React.Component { render() { const filenames = this.props.pending; - return this.props.updateAll(this.props.type)} : null}> + return this.props.updateAll(this.props.type)} : null}> {!filenames.length && } {filenames.map(f => { const info = this.props.updater.cache[f]; const addon = this.props.updater.manager.addonList.find(a => a.filename === f); - return - + return + ; })} ; @@ -79,11 +79,11 @@ export default class UpdaterPanel extends React.Component { } async checkForUpdates() { - Toasts.info("Checking for updates!"); + Toasts.info(Strings.Updater.checking); await this.checkCoreUpdate(); await this.checkAddons("plugins"); await this.checkAddons("themes"); - Toasts.info("Finished checking for updates!"); + Toasts.info(Strings.Updater.finishedChecking); } async checkCoreUpdate() { @@ -120,7 +120,7 @@ export default class UpdaterPanel extends React.Component { render() { return [ - , + , , , ,