From 87652dab8e0edf3289340c839d3a515da93eaba1 Mon Sep 17 00:00:00 2001 From: Zack Rauen Date: Sun, 2 Oct 2022 03:34:34 -0400 Subject: [PATCH] Restructure API, condense DOM modules --- package.json | 2 +- renderer/src/builtins/customcss.js | 2 +- renderer/src/builtins/developer/debuglogs.js | 8 +- renderer/src/builtins/emotes/emotemenu.js | 6 +- renderer/src/builtins/emotes/emotes.js | 10 +- .../src/builtins/general/publicservers.js | 12 +- renderer/src/index.js | 2 +- renderer/src/modules/addonmanager.js | 12 +- renderer/src/modules/api/addonapi.js | 64 ++ renderer/src/modules/api/data.js | 55 ++ renderer/src/modules/api/dom.js | 101 +++ renderer/src/modules/api/index.js | 113 +++ renderer/src/modules/api/legacy.js | 438 ++++++++++ renderer/src/modules/api/patcher.js | 83 ++ renderer/src/modules/api/reactutils.js | 82 ++ renderer/src/modules/api/ui.js | 116 +++ renderer/src/modules/api/utils.js | 73 ++ renderer/src/modules/api/webpack.js | 111 +++ renderer/src/modules/datastore.js | 4 +- renderer/src/modules/dommanager.js | 99 ++- renderer/src/modules/domtools.js | 750 ------------------ renderer/src/modules/modules.js | 1 - renderer/src/modules/pluginapi.js | 708 ----------------- renderer/src/modules/utilities.js | 195 ++--- renderer/src/ui/floating/container.jsx | 4 +- renderer/src/ui/floatingwindows.js | 4 +- renderer/src/ui/modals.js | 4 +- renderer/src/ui/notices.js | 6 +- renderer/src/ui/settings.js | 2 +- renderer/src/ui/tooltip.js | 10 +- 30 files changed, 1441 insertions(+), 1636 deletions(-) create mode 100644 renderer/src/modules/api/addonapi.js create mode 100644 renderer/src/modules/api/data.js create mode 100644 renderer/src/modules/api/dom.js create mode 100644 renderer/src/modules/api/index.js create mode 100644 renderer/src/modules/api/legacy.js create mode 100644 renderer/src/modules/api/patcher.js create mode 100644 renderer/src/modules/api/reactutils.js create mode 100644 renderer/src/modules/api/ui.js create mode 100644 renderer/src/modules/api/utils.js create mode 100644 renderer/src/modules/api/webpack.js delete mode 100644 renderer/src/modules/domtools.js delete mode 100644 renderer/src/modules/pluginapi.js diff --git a/package.json b/package.json index cd8e8dad..81b3f853 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "lint": "eslint --ext .js common/ && pnpm --filter injector lint && pnpm --filter preload lint && pnpm --filter renderer lint-js", "test": "mocha --require @babel/register --recursive \"./tests/renderer/*.js\"", "dist": "pnpm run build-prod && node scripts/pack.js", - "api": "jsdoc -X renderer/src/modules/pluginapi.js > jsdoc-ast.json" + "api": "jsdoc -X -r renderer/src/modules/api/ > jsdoc-ast.json" }, "devDependencies": { "asar": "^3.2.0", diff --git a/renderer/src/builtins/customcss.js b/renderer/src/builtins/customcss.js index f2e4a8da..6f830f1c 100644 --- a/renderer/src/builtins/customcss.js +++ b/renderer/src/builtins/customcss.js @@ -39,7 +39,7 @@ export default new class CustomCSS extends Builtin { if (this.isDetached) return; if (this.nativeOpen) return this.openNative(); else if (this.startDetached) return this.openDetached(this.savedCss); - const settingsView = Utilities.findInRenderTree(thisObject._reactInternals, m => m && m.onSetSection, {walkable: ["child", "memoizedProps", "props", "children"]}); + const settingsView = Utilities.findInTree(thisObject._reactInternals, m => m && m.onSetSection, {walkable: ["child", "memoizedProps", "props", "children"]}); if (settingsView && settingsView.onSetSection) settingsView.onSetSection(this.id); } }); diff --git a/renderer/src/builtins/developer/debuglogs.js b/renderer/src/builtins/developer/debuglogs.js index 03404328..d10a58bb 100644 --- a/renderer/src/builtins/developer/debuglogs.js +++ b/renderer/src/builtins/developer/debuglogs.js @@ -2,7 +2,6 @@ const fs = require("fs"); const path = require("path"); import Builtin from "../../structs/builtin"; import DataStore from "../../modules/datastore"; -import Utilities from "../../modules/utilities"; const timestamp = () => new Date().toISOString().replace("T", " ").replace("Z", ""); @@ -18,6 +17,11 @@ const getCircularReplacer = () => { }; }; +const occurrences = (source, substring) => { + const regex = new RegExp(substring, "g"); + return (source.match(regex) || []).length; +}; + export default new class DebugLogs extends Builtin { get name() {return "DebugLogs";} get category() {return "developer";} @@ -45,7 +49,7 @@ export default new class DebugLogs extends Builtin { for (let i = 0; i < args.length; i++) { const arg = args[i]; if (typeof(arg) === "string") { - const styleCount = Utilities.occurrences(arg, "%c"); + const styleCount = occurrences(arg, "%c"); sanitized.push(arg.replace(/%c/g, "")); if (styleCount > 0) i += styleCount; } diff --git a/renderer/src/builtins/emotes/emotemenu.js b/renderer/src/builtins/emotes/emotemenu.js index 0ff37072..f1c37bfd 100644 --- a/renderer/src/builtins/emotes/emotemenu.js +++ b/renderer/src/builtins/emotes/emotemenu.js @@ -20,7 +20,7 @@ export default new class EmoteMenu extends Builtin { enabled() { this.after(EmojiPicker, "type", (_, __, returnValue) => { - const originalChildren = Utilities.getNestedProp(returnValue, "props.children.props.children"); + const originalChildren = returnValue?.props?.children?.props?.children; if (!originalChildren || originalChildren.__patched) return; const activePicker = useExpressionPickerStore((state) => state.activeView); @@ -30,8 +30,8 @@ export default new class EmoteMenu extends Builtin { // Attach a try {} catch {} because this might crash the user. try { - const head = Utilities.findInReactTree(childrenReturn, (e) => e?.role === "tablist")?.children; - const body = Utilities.findInReactTree(childrenReturn, (e) => e?.[0]?.type === "nav"); + const head = Utilities.findInTree(childrenReturn, (e) => e?.role === "tablist", {walkable: ["props", "children", "return", "stateNode"]})?.children; + const body = Utilities.findInTree(childrenReturn, (e) => e?.[0]?.type === "nav", {walkable: ["props", "children", "return", "stateNode"]}); if (!head || !body) return childrenReturn; const isActive = activePicker == "bd-emotes"; diff --git a/renderer/src/builtins/emotes/emotes.js b/renderer/src/builtins/emotes/emotes.js index a8e0bad7..1afef9f1 100644 --- a/renderer/src/builtins/emotes/emotes.js +++ b/renderer/src/builtins/emotes/emotes.js @@ -1,7 +1,7 @@ import Builtin from "../../structs/builtin"; import {EmoteConfig, Config} from "data"; -import {Utilities, WebpackModules, DataStore, DiscordModules, Events, Settings, Strings} from "modules"; +import {WebpackModules, DataStore, DiscordModules, Events, Settings, Strings} from "modules"; import BDEmote from "../../ui/emote"; import Modals from "../../ui/modals"; import Toasts from "../../ui/toasts"; @@ -24,6 +24,10 @@ const Emotes = { FrankerFaceZ: {} }; +const escape = (s) => { + return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); +}; + const blocklist = []; const overrides = ["twitch", "subscriber", "bttv", "ffz"]; const modifiers = ["flip", "spin", "pulse", "spin2", "spin3", "1spin", "2spin", "3spin", "tr", "bl", "br", "shake", "shake2", "shake3", "flap"]; @@ -53,7 +57,7 @@ const modifiers = ["flip", "spin", "pulse", "spin2", "spin3", "1spin", "2spin", getUrl(category, name) {return EmoteURLs[category].format({id: Emotes[category][name]});} getCategory(category) {return Emotes[category];} - getRemoteFile(category) {return Utilities.repoUrl(`assets/emotes/${category.toLowerCase()}.json`);} + getRemoteFile(category) {return `https://cdn.staticaly.com/gh/BetterDiscord/BetterDiscord/${Config.hash}/assets/emotes/${category.toLowerCase()}.json`;} initialize() { super.initialize(); @@ -154,7 +158,7 @@ const modifiers = ["flip", "spin", "pulse", "spin2", "spin3", "1spin", "2spin", } if (!Emotes[current][emoteName]) continue; - const results = nodes[n].match(new RegExp(`([\\s]|^)${Utilities.escape(emoteModifier ? emoteName + ":" + emoteModifier : emoteName)}([\\s]|$)`)); + const results = nodes[n].match(new RegExp(`([\\s]|^)${escape(emoteModifier ? emoteName + ":" + emoteModifier : emoteName)}([\\s]|$)`)); if (!results) continue; const pre = nodes[n].substring(0, results.index + results[1].length); const post = nodes[n].substring(results.index + results[0].length - results[2].length); diff --git a/renderer/src/builtins/general/publicservers.js b/renderer/src/builtins/general/publicservers.js index 395acb4b..b5b350fb 100644 --- a/renderer/src/builtins/general/publicservers.js +++ b/renderer/src/builtins/general/publicservers.js @@ -1,5 +1,5 @@ import Builtin from "../../structs/builtin"; -import {DiscordModules, WebpackModules, Strings, DOM, React} from "modules"; +import {DiscordModules, WebpackModules, Strings, DOMManager, React} from "modules"; import PublicServersMenu from "../../ui/publicservers/menu"; import Globe from "../../ui/icons/globe"; @@ -80,13 +80,13 @@ export default new class PublicServers extends Builtin { async _appendButton() { await new Promise(r => setTimeout(r, 1000)); - const existing = DOM.query("#bd-pub-li"); + const existing = document.querySelector("#bd-pub-li"); if (existing) return; - const guilds = DOM.query(`.${DiscordModules.GuildClasses.guilds} .${DiscordModules.GuildClasses.listItem}`); + const guilds = document.querySelector(`.${DiscordModules.GuildClasses.guilds} .${DiscordModules.GuildClasses.listItem}`); if (!guilds) return; - DOM.after(guilds, this.button); + guilds.parentNode.insertBefore(this.button, guilds.nextSibling); } openPublicServers() { @@ -94,8 +94,8 @@ export default new class PublicServers extends Builtin { } get button() { - const btn = DOM.createElement(`
`); - const label = DOM.createElement(`
${Strings.PublicServers.button}
`); + const btn = DOMManager.parseHTML(`
`); + const label = DOMManager.parseHTML(`
${Strings.PublicServers.button}
`); label.addEventListener("click", () => {this.openPublicServers();}); btn.append(label); return btn; diff --git a/renderer/src/index.js b/renderer/src/index.js index 21208310..65173ca5 100644 --- a/renderer/src/index.js +++ b/renderer/src/index.js @@ -2,7 +2,7 @@ import require from "./polyfill"; // eslint-disable-line no-unused-vars import secure from "./secure"; import LoadingIcon from "./loadingicon"; import BetterDiscord from "./modules/core"; -import BdApi from "./modules/pluginapi"; +import BdApi from "./modules/api/index"; // Perform some setup secure(); diff --git a/renderer/src/modules/addonmanager.js b/renderer/src/modules/addonmanager.js index 7eed3965..a58d5e19 100644 --- a/renderer/src/modules/addonmanager.js +++ b/renderer/src/modules/addonmanager.js @@ -1,4 +1,3 @@ -import Utilities from "./utilities"; import Logger from "common/logger"; import Settings from "./settingsmanager"; import Events from "./emitter"; @@ -138,9 +137,14 @@ export default class AddonManager { parseOldMeta(fileContent, filename) { const meta = fileContent.split("\n")[0]; const metaData = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//")); - const parsed = Utilities.testJSON(metaData); - if (!parsed) throw new AddonError(filename, filename, Strings.Addons.metaError, {message: "", stack: meta}, this.prefix); - if (!parsed.name) throw new AddonError(filename, filename, Strings.Addons.missingNameData, {message: "", stack: meta}, this.prefix); + let parsed = null; + try { + parsed = JSON.parse(metaData); + } + catch (err) { + throw new AddonError(filename, filename, Strings.Addons.metaError, err, this.prefix); + } + if (!parsed || !parsed.name) throw new AddonError(filename, filename, Strings.Addons.missingNameData, {message: "", stack: meta}, this.prefix); parsed.format = "json"; return parsed; } diff --git a/renderer/src/modules/api/addonapi.js b/renderer/src/modules/api/addonapi.js new file mode 100644 index 00000000..295551ab --- /dev/null +++ b/renderer/src/modules/api/addonapi.js @@ -0,0 +1,64 @@ +/** + * `AddonAPI` is a utility class for working with plugins and themes. Instances are accessible through the {@link BdApi}. + * @name AddonAPI + */ + class AddonAPI { + #manager; + + constructor(manager) {this.#manager = manager;} + + /** + * The path to the addon folder. + * @type string + */ + get folder() {return this.#manager.addonFolder;} + + /** + * Determines if a particular adon is enabled. + * @param {string} idOrFile Addon id or filename. + * @returns {boolean} + */ + isEnabled(idOrFile) {return this.#manager.isEnabled(idOrFile);} + + /** + * Enables the given addon. + * @param {string} idOrFile Addon id or filename. + */ + enable(idOrAddon) {return this.#manager.enableAddon(idOrAddon);} + + /** + * Disables the given addon. + * @param {string} idOrFile Addon id or filename. + */ + disable(idOrAddon) {return this.#manager.disableAddon(idOrAddon);} + + /** + * Toggles if a particular addon is enabled. + * @param {string} idOrFile Addon id or filename. + */ + toggle(idOrAddon) {return this.#manager.toggleAddon(idOrAddon);} + + /** + * Reloads if a particular addon is enabled. + * @param {string} idOrFile Addon id or filename. + */ + reload(idOrFileOrAddon) {return this.#manager.reloadAddon(idOrFileOrAddon);} + + /** + * Gets a particular addon. + * @param {string} idOrFile Addon id or filename. + * @returns {object} Addon instance + */ + get(idOrFile) {return this.#manager.getAddon(idOrFile);} + + /** + * Gets all addons of this type. + * @returns {Array} Array of all addon instances + */ + getAll() {return this.#manager.addonList.map(a => this.#manager.getAddon(a.id));} +} + +Object.freeze(AddonAPI); +Object.freeze(AddonAPI.prototype); + +export default AddonAPI; \ No newline at end of file diff --git a/renderer/src/modules/api/data.js b/renderer/src/modules/api/data.js new file mode 100644 index 00000000..b72f873f --- /dev/null +++ b/renderer/src/modules/api/data.js @@ -0,0 +1,55 @@ +import DataStore from "../datastore"; + +/** + * `Data` is a simple utility class for the management of plugin data. An instance is available on {@link BdApi}. + * @type Data + * @summary {@link Data} is a simple utility class for the management of plugin data. + * @name Data + */ +class Data { + + constructor(callerName) { + if (!callerName) return; + this.save = this.save.bind(this, callerName); + this.load = this.load.bind(this, callerName); + this.delete = this.delete.bind(this, callerName); + } + + /** + * Saves JSON-serializable data. + * + * @param {string} pluginName Name of the plugin saving data + * @param {string} key Which piece of data to store + * @param {any} data The data to be saved + * @returns + */ + save(pluginName, key, data) { + return DataStore.setPluginData(pluginName, key, data); + } + + /** + * Loads previously stored data. + * + * @param {string} pluginName Name of the plugin loading data + * @param {string} key Which piece of data to load + * @returns {any} The stored data + */ + load(pluginName, key, data) { + return DataStore.setPluginData(pluginName, key, data); + } + + /** + * Deletes a piece of stored data, this is different than saving as null or undefined. + * + * @param {string} pluginName Name of the plugin deleting data + * @param {string} key Which piece of data to delete + */ + delete(pluginName, key, data) { + return DataStore.setPluginData(pluginName, key, data); + } + +} + +Object.freeze(Data); + +export default Data; \ No newline at end of file diff --git a/renderer/src/modules/api/dom.js b/renderer/src/modules/api/dom.js new file mode 100644 index 00000000..b496e786 --- /dev/null +++ b/renderer/src/modules/api/dom.js @@ -0,0 +1,101 @@ +import DOMManager from "../dommanager"; + +/** + * `DOM` is a simple utility class for dom manipulation. An instance is available on {@link BdApi}. + * @type DOM + * @summary {@link DOM} is a simple utility class for dom manipulation. + * @name DOM + */ +class DOM { + + /** Document/window width */ + get screenWidth() {return Math.max(document.documentElement.clientWidth, window.innerWidth || 0);} + + /** Document/window height */ + get screenHeight() {return Math.max(document.documentElement.clientHeight, window.innerHeight || 0);} + + constructor(callerName) { + if (!callerName) return; + this.addStyle = this.addStyle.bind(this, callerName); + this.removeStyle = this.removeStyle.bind(this, callerName); + } + + /** + * Adds a ``)); - } - - /** - * Removes a style from the document. - * @param {string} id - original identifier used - */ - static removeStyle(id) { - const element = document.getElementById(id); - if (element) element.remove(); - } - - /** - * Adds/requires a remote script to be loaded - * @param {string} id - identifier to use for this script - * @param {string} url - url from which to load the script - * @returns {Promise} promise that resolves when the script is loaded - */ - static addScript(id, url) { - return new Promise(resolve => { - const script = document.createElement("script"); - script.id = id; - script.src = url; - script.type = "text/javascript"; - script.onload = resolve; - document.head.append(script); - }); - } - - /** - * Removes a remote script from the document. - * @param {string} id - original identifier used - */ - static removeScript(id) { - id = this.escapeID(id); - const element = document.getElementById(id); - if (element) element.remove(); - } - - /** - * This is my shit version of not having to use `$` from jQuery. Meaning - * that you can pass a selector and it will automatically run {@link module:DOMTools.query}. - * It also means that you can pass a string of html and it will perform and return `parseHTML`. - * @see module:DOMTools.parseHTML - * @see module:DOMTools.query - * @param {string} selector - Selector to query or HTML to parse - * @returns {(DocumentFragment|NodeList|HTMLElement)} - Either the result of `parseHTML` or `query` - */ - static Q(selector) { - const element = this.parseHTML(selector); - const isHTML = element instanceof NodeList ? Array.from(element).some(n => n.nodeType === 1) : element.nodeType === 1; - if (isHTML) return element; - return this.query(selector); - } - - /** - * Essentially a shorthand for `document.querySelector`. If the `baseElement` is not provided - * `document` is used by default. - * @param {string} selector - Selector to query - * @param {Element} [baseElement] - Element to base the query from - * @returns {(Element|null)} - The found element or null if not found - */ - static query(selector, baseElement) { - if (!baseElement) baseElement = document; - return baseElement.querySelector(selector); - } - - /** - * Essentially a shorthand for `document.querySelectorAll`. If the `baseElement` is not provided - * `document` is used by default. - * @param {string} selector - Selector to query - * @param {Element} [baseElement] - Element to base the query from - * @returns {Array} - Array of all found elements - */ - static queryAll(selector, baseElement) { - if (!baseElement) baseElement = document; - return baseElement.querySelectorAll(selector); - } - - /** - * Parses a string of HTML and returns the results. If the second parameter is true, - * the parsed HTML will be returned as a document fragment {@see https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment}. - * This is extremely useful if you have a list of elements at the top level, they can then be appended all at once to another node. - * - * If the second parameter is false, then the return value will be the list of parsed - * nodes and there were multiple top level nodes, otherwise the single node is returned. - * @param {string} html - HTML to be parsed - * @param {boolean} [fragment=false] - Whether or not the return should be the raw `DocumentFragment` - * @returns {(DocumentFragment|NodeList|HTMLElement)} - The result of HTML parsing - */ - static parseHTML(html, fragment = false) { - const template = document.createElement("template"); - template.innerHTML = html; - const node = template.content.cloneNode(true); - if (fragment) return node; - return node.childNodes.length > 1 ? node.childNodes : node.childNodes[0]; - } - - /** Alternate name for {@link module:DOMTools.parseHTML} */ - static createElement(html, fragment = false) {return this.parseHTML(html, fragment);} - - /** - * Takes a string of html and escapes it using the brower's own escaping mechanism. - * @param {String} html - html to be escaped - */ - static escapeHTML(html) { - const textNode = document.createTextNode(""); - const spanElement = document.createElement("span"); - spanElement.append(textNode); - textNode.nodeValue = html; - return spanElement.innerHTML; - } - - /** - * Adds a list of classes from the target element. - * @param {Element} element - Element to edit classes of - * @param {...string} classes - Names of classes to add - * @returns {Element} - `element` to allow for chaining - */ - static addClass(element, ...classes) { - classes = classes.flat().filter(c => c); - for (let c = 0; c < classes.length; c++) classes[c] = classes[c].toString().split(" "); - classes = classes.flat().filter(c => c); - element.classList.add(...classes); - return element; - } - - /** - * Removes a list of classes from the target element. - * @param {Element} element - Element to edit classes of - * @param {...string} classes - Names of classes to remove - * @returns {Element} - `element` to allow for chaining - */ - static removeClass(element, ...classes) { - for (let c = 0; c < classes.length; c++) classes[c] = classes[c].toString().split(" "); - classes = classes.flat().filter(c => c); - element.classList.remove(...classes); - return element; - } - - /** - * When only one argument is present: Toggle class value; - * i.e., if class exists then remove it and return false, if not, then add it and return true. - * When a second argument is present: - * If the second argument evaluates to true, add specified class value, and if it evaluates to false, remove it. - * @param {Element} element - Element to edit classes of - * @param {string} classname - Name of class to toggle - * @param {boolean} [indicator] - Optional indicator for if the class should be toggled - * @returns {Element} - `element` to allow for chaining - */ - static toggleClass(element, classname, indicator) { - classname = classname.toString().split(" ").filter(c => c); - if (typeof(indicator) !== "undefined") classname.forEach(c => element.classList.toggle(c, indicator)); - else classname.forEach(c => element.classList.toggle(c)); - return element; - } - - /** - * Checks if an element has a specific class - * @param {Element} element - Element to edit classes of - * @param {string} classname - Name of class to check - * @returns {boolean} - `true` if the element has the class, `false` otherwise. - */ - static hasClass(element, classname) { - return classname.toString().split(" ").filter(c => c).every(c => element.classList.contains(c)); - } - - /** - * Replaces one class with another - * @param {Element} element - Element to edit classes of - * @param {string} oldName - Name of class to replace - * @param {string} newName - New name for the class - * @returns {Element} - `element` to allow for chaining - */ - static replaceClass(element, oldName, newName) { - element.classList.replace(oldName, newName); - return element; - } - - /** - * Appends `thisNode` to `thatNode` - * @param {Node} thisNode - Node to be appended to another node - * @param {Node} thatNode - Node for `thisNode` to be appended to - * @returns {Node} - `thisNode` to allow for chaining - */ - static appendTo(thisNode, thatNode) { - if (typeof(thatNode) == "string") thatNode = this.query(thatNode); - if (!thatNode) return null; - thatNode.append(thisNode); - return thisNode; - } - - /** - * Prepends `thisNode` to `thatNode` - * @param {Node} thisNode - Node to be prepended to another node - * @param {Node} thatNode - Node for `thisNode` to be prepended to - * @returns {Node} - `thisNode` to allow for chaining - */ - static prependTo(thisNode, thatNode) { - if (typeof(thatNode) == "string") thatNode = this.query(thatNode); - if (!thatNode) return null; - thatNode.prepend(thisNode); - return thisNode; - } - - /** - * Insert after a specific element, similar to jQuery's `thisElement.insertAfter(otherElement)`. - * @param {Node} thisNode - The node to insert - * @param {Node} targetNode - Node to insert after in the tree - * @returns {Node} - `thisNode` to allow for chaining - */ - static insertAfter(thisNode, targetNode) { - targetNode.parentNode.insertBefore(thisNode, targetNode.nextSibling); - return thisNode; - } - - /** - * Insert after a specific element, similar to jQuery's `thisElement.after(newElement)`. - * @param {Node} thisNode - The node to insert - * @param {Node} newNode - Node to insert after in the tree - * @returns {Node} - `thisNode` to allow for chaining - */ - static after(thisNode, newNode) { - thisNode.parentNode.insertBefore(newNode, thisNode.nextSibling); - return thisNode; - } - - /** - * Gets the next sibling element that matches the selector. - * @param {Element} element - Element to get the next sibling of - * @param {string} [selector=""] - Optional selector - * @returns {Element} - The sibling element - */ - static next(element, selector = "") { - return selector ? element.querySelector("+ " + selector) : element.nextElementSibling; - } - - /** - * Gets all subsequent siblings. - * @param {Element} element - Element to get next siblings of - * @returns {NodeList} - The list of siblings - */ - static nextAll(element) { - return element.querySelectorAll("~ *"); - } - - /** - * Gets the subsequent siblings until an element matches the selector. - * @param {Element} element - Element to get the following siblings of - * @param {string} selector - Selector to stop at - * @returns {Array} - The list of siblings - */ - static nextUntil(element, selector) { - const next = []; - while (element.nextElementSibling && !element.nextElementSibling.matches(selector)) next.push(element = element.nextElementSibling); - return next; - } - - /** - * Gets the previous sibling element that matches the selector. - * @param {Element} element - Element to get the previous sibling of - * @param {string} [selector=""] - Optional selector - * @returns {Element} - The sibling element - */ - static previous(element, selector = "") { - const previous = element.previousElementSibling; - if (selector) return previous && previous.matches(selector) ? previous : null; - return previous; - } - - /** - * Gets all preceeding siblings. - * @param {Element} element - Element to get preceeding siblings of - * @returns {NodeList} - The list of siblings - */ - static previousAll(element) { - const previous = []; - while (element.previousElementSibling) previous.push(element = element.previousElementSibling); - return previous; - } - - /** - * Gets the preceeding siblings until an element matches the selector. - * @param {Element} element - Element to get the preceeding siblings of - * @param {string} selector - Selector to stop at - * @returns {Array} - The list of siblings - */ - static previousUntil(element, selector) { - const previous = []; - while (element.previousElementSibling && !element.previousElementSibling.matches(selector)) previous.push(element = element.previousElementSibling); - return previous; - } - - /** - * Find which index in children a certain node is. Similar to jQuery's `$.index()` - * @param {HTMLElement} node - The node to find its index in parent - * @returns {number} Index of the node - */ - static indexInParent(node) { - const children = node.parentNode.childNodes; - let num = 0; - for (let i = 0; i < children.length; i++) { - if (children[i] == node) return num; - if (children[i].nodeType == 1) num++; - } - return -1; - } - - /** Shorthand for {@link module:DOMTools.indexInParent} */ - static index(node) {return this.indexInParent(node);} - - /** - * Gets the parent of the element if it matches the selector, - * otherwise returns null. - * @param {Element} element - Element to get parent of - * @param {string} [selector=""] - Selector to match parent - * @returns {(Element|null)} - The sibling element or null - */ - static parent(element, selector = "") { - return !selector || element.parentElement.matches(selector) ? element.parentElement : null; - } - - /** - * Gets all children of Element that match the selector if provided. - * @param {Element} element - Element to get all children of - * @param {string} selector - Selector to match the children to - * @returns {Array} - The list of children - */ - static findChild(element, selector) { - return element.querySelector(":scope > " + selector); - } - - /** - * Gets all children of Element that match the selector if provided. - * @param {Element} element - Element to get all children of - * @param {string} selector - Selector to match the children to - * @returns {Array} - The list of children - */ - static findChildren(element, selector) { - return element.querySelectorAll(":scope > " + selector); - } - - /** - * Gets all ancestors of Element that match the selector if provided. - * @param {Element} element - Element to get all parents of - * @param {string} [selector=""] - Selector to match the parents to - * @returns {Array} - The list of parents - */ - static parents(element, selector = "") { - const parents = []; - if (selector) while (element.parentElement && element.parentElement.closest(selector)) parents.push(element = element.parentElement.closest(selector)); - else while (element.parentElement) parents.push(element = element.parentElement); - return parents; - } - - /** - * Gets the ancestors until an element matches the selector. - * @param {Element} element - Element to get the ancestors of - * @param {string} selector - Selector to stop at - * @returns {Array} - The list of parents - */ - static parentsUntil(element, selector) { - const parents = []; - while (element.parentElement && !element.parentElement.matches(selector)) parents.push(element = element.parentElement); - return parents; - } - - /** - * Gets all siblings of the element that match the selector. - * @param {Element} element - Element to get all siblings of - * @param {string} [selector="*"] - Selector to match the siblings to - * @returns {Array} - The list of siblings - */ - static siblings(element, selector = "*") { - return Array.from(element.parentElement.children).filter(e => e != element && e.matches(selector)); - } - - /** - * Sets or gets css styles for a specific element. If `value` is provided - * then it sets the style and returns the element to allow for chaining, - * otherwise returns the style. - * @param {Element} element - Element to set the CSS of - * @param {string} attribute - Attribute to get or set - * @param {string} [value] - Value to set for attribute - * @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned. - */ - static css(element, attribute, value) { - if (typeof(value) == "undefined") return global.getComputedStyle(element)[attribute]; - element.style[attribute] = value; - return element; - } - - /** - * Sets or gets the width for a specific element. If `value` is provided - * then it sets the width and returns the element to allow for chaining, - * otherwise returns the width. - * @param {Element} element - Element to set the CSS of - * @param {string} [value] - Width to set - * @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned. - */ - static width(element, value) { - if (typeof(value) == "undefined") return parseInt(getComputedStyle(element).width); - element.style.width = value; - return element; - } - - /** - * Sets or gets the height for a specific element. If `value` is provided - * then it sets the height and returns the element to allow for chaining, - * otherwise returns the height. - * @param {Element} element - Element to set the CSS of - * @param {string} [value] - Height to set - * @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned. - */ - static height(element, value) { - if (typeof(value) == "undefined") return parseInt(getComputedStyle(element).height); - element.style.height = value; - return element; - } - - /** - * Sets the inner text of an element if given a value, otherwise returns it. - * @param {Element} element - Element to set the text of - * @param {string} [text] - Content to set - * @returns {string} - Either the string set by this call or the current text content of the node. - */ - static text(element, text) { - if (typeof(text) == "undefined") return element.textContent; - return element.textContent = text; - } - - /** - * Returns the innerWidth of the element. - * @param {Element} element - Element to retrieve inner width of - * @return {number} - The inner width of the element. - */ - static innerWidth(element) { - return element.clientWidth; - } - - /** - * Returns the innerHeight of the element. - * @param {Element} element - Element to retrieve inner height of - * @return {number} - The inner height of the element. - */ - static innerHeight(element) { - return element.clientHeight; - } - - /** - * Returns the outerWidth of the element. - * @param {Element} element - Element to retrieve outer width of - * @return {number} - The outer width of the element. - */ - static outerWidth(element) { - return element.offsetWidth; - } - - /** - * Returns the outerHeight of the element. - * @param {Element} element - Element to retrieve outer height of - * @return {number} - The outer height of the element. - */ - static outerHeight(element) { - return element.offsetHeight; - } - - /** - * Gets the offset of the element in the page. - * @param {Element} element - Element to get offset of - * @return {Offset} - The offset of the element - */ - static offset(element) { - return element.getBoundingClientRect(); - } - - static get listeners() {return this._listeners || (this._listeners = {});} - - /** - * This is similar to jQuery's `on` function and can *hopefully* be used in the same way. - * - * Rather than attempt to explain, I'll show some example usages. - * - * The following will add a click listener (in the `myPlugin` namespace) to `element`. - * `DOMTools.on(element, "click.myPlugin", () => {console.log("clicked!");});` - * - * The following will add a click listener (in the `myPlugin` namespace) to `element` that only fires when the target is a `.block` element. - * `DOMTools.on(element, "click.myPlugin", ".block", () => {console.log("clicked!");});` - * - * The following will add a click listener (without namespace) to `element`. - * `DOMTools.on(element, "click", () => {console.log("clicked!");});` - * - * The following will add a click listener (without namespace) to `element` that only fires once. - * `const cancel = DOMTools.on(element, "click", () => {console.log("fired!"); cancel();});` - * - * @param {Element} element - Element to add listener to - * @param {string} event - Event to listen to with option namespace (e.g. "event.namespace") - * @param {(string|callable)} delegate - Selector to run on element to listen to - * @param {callable} [callback] - Function to fire on event - * @returns {module:DOMTools~CancelListener} - A function that will undo the listener - */ - static on(element, event, delegate, callback) { - const [type, namespace] = event.split("."); - const hasDelegate = delegate && callback; - if (!callback) callback = delegate; - const eventFunc = !hasDelegate ? callback : function(ev) { - if (ev.target.matches(delegate)) { - callback(ev); - } - }; - - element.addEventListener(type, eventFunc); - const cancel = () => { - element.removeEventListener(type, eventFunc); - }; - if (namespace) { - if (!this.listeners[namespace]) this.listeners[namespace] = []; - const newCancel = () => { - cancel(); - this.listeners[namespace].splice(this.listeners[namespace].findIndex(l => l.event == type && l.element == element), 1); - }; - this.listeners[namespace].push({ - event: type, - element: element, - cancel: newCancel - }); - return newCancel; - } - return cancel; - } - - /** - * Functionality for this method matches {@link module:DOMTools.on} but automatically cancels itself - * and removes the listener upon the first firing of the desired event. - * - * @param {Element} element - Element to add listener to - * @param {string} event - Event to listen to with option namespace (e.g. "event.namespace") - * @param {(string|callable)} delegate - Selector to run on element to listen to - * @param {callable} [callback] - Function to fire on event - * @returns {module:DOMTools~CancelListener} - A function that will undo the listener - */ - static once(element, event, delegate, callback) { - const [type, namespace] = event.split("."); - const hasDelegate = delegate && callback; - if (!callback) callback = delegate; - const eventFunc = !hasDelegate ? function(ev) { - callback(ev); - element.removeEventListener(type, eventFunc); - } : function(ev) { - if (!ev.target.matches(delegate)) return; - callback(ev); - element.removeEventListener(type, eventFunc); - }; - - element.addEventListener(type, eventFunc); - const cancel = () => { - element.removeEventListener(type, eventFunc); - }; - if (namespace) { - if (!this.listeners[namespace]) this.listeners[namespace] = []; - const newCancel = () => { - cancel(); - this.listeners[namespace].splice(this.listeners[namespace].findIndex(l => l.event == type && l.element == element), 1); - }; - this.listeners[namespace].push({ - event: type, - element: element, - cancel: newCancel - }); - return newCancel; - } - return cancel; - } - - static __offAll(event, element) { - const [type, namespace] = event.split("."); - let matchFilter = listener => listener.event == type, defaultFilter = _ => _; - if (element) { - matchFilter = l => l.event == type && l.element == element; - defaultFilter = l => l.element == element; - } - const listeners = this.listeners[namespace] || []; - const list = type ? listeners.filter(matchFilter) : listeners.filter(defaultFilter); - for (let c = 0; c < list.length; c++) list[c].cancel(); - } - - /** - * This is similar to jQuery's `off` function and can *hopefully* be used in the same way. - * - * Rather than attempt to explain, I'll show some example usages. - * - * The following will remove a click listener called `onClick` (in the `myPlugin` namespace) from `element`. - * `DOMTools.off(element, "click.myPlugin", onClick);` - * - * The following will remove a click listener called `onClick` (in the `myPlugin` namespace) from `element` that only fired when the target is a `.block` element. - * `DOMTools.off(element, "click.myPlugin", ".block", onClick);` - * - * The following will remove a click listener (without namespace) from `element`. - * `DOMTools.off(element, "click", onClick);` - * - * The following will remove all listeners in namespace `myPlugin` from `element`. - * `DOMTools.off(element, ".myPlugin");` - * - * The following will remove all click listeners in namespace `myPlugin` from *all elements*. - * `DOMTools.off("click.myPlugin");` - * - * The following will remove all listeners in namespace `myPlugin` from *all elements*. - * `DOMTools.off(".myPlugin");` - * - * @param {(Element|string)} element - Element to remove listener from - * @param {string} [event] - Event to listen to with option namespace (e.g. "event.namespace") - * @param {(string|callable)} [delegate] - Selector to run on element to listen to - * @param {callable} [callback] - Function to fire on event - * @returns {Element} - The original element to allow for chaining - */ - static off(element, event, delegate, callback) { - if (typeof(element) == "string") return this.__offAll(element); - const [type, namespace] = event.split("."); - if (namespace) return this.__offAll(event, element); - - const hasDelegate = delegate && callback; - if (!callback) callback = delegate; - const eventFunc = !hasDelegate ? callback : function(ev) { - if (ev.target.matches(delegate)) { - callback(ev); - } - }; - - element.removeEventListener(type, eventFunc); - return element; - } - - /** - * Adds a listener for when the node is added/removed from the document body. - * The listener is automatically removed upon firing. - * @param {HTMLElement} node - node to wait for - * @param {callable} callback - function to be performed on event - * @param {boolean} onMount - determines if it should fire on Mount or on Unmount - */ - static onMountChange(node, callback, onMount = true) { - const wrappedCallback = () => { - this.observer.unsubscribe(wrappedCallback); - callback(); - }; - this.observer.subscribe(wrappedCallback, mutation => { - const nodes = Array.from(onMount ? mutation.addedNodes : mutation.removedNodes); - const directMatch = nodes.indexOf(node) > -1; - const parentMatch = nodes.some(parent => parent.contains(node)); - return directMatch || parentMatch; - }); - return node; - } - - /** Shorthand for {@link module:DOMTools.onMountChange} with third parameter `true` */ - static onMount(node, callback) {return this.onMountChange(node, callback);} - - /** Shorthand for {@link module:DOMTools.onMountChange} with third parameter `false` */ - static onUnmount(node, callback) {return this.onMountChange(node, callback, false);} - - /** Alias for {@link module:DOMTools.onMount} */ - static onAdded(node, callback) {return this.onMount(node, callback);} - - /** Alias for {@link module:DOMTools.onUnmount} */ - static onRemoved(node, callback) {return this.onUnmount(node, callback, false);} - - /** - * Helper function which combines multiple elements into one parent element - * @param {Array} elements - array of elements to put into a single parent - */ - static wrap(elements) { - const domWrapper = this.parseHTML(`
`); - for (let e = 0; e < elements.length; e++) domWrapper.appendChild(elements[e]); - return domWrapper; - } -} \ No newline at end of file diff --git a/renderer/src/modules/modules.js b/renderer/src/modules/modules.js index 62215184..b1377797 100644 --- a/renderer/src/modules/modules.js +++ b/renderer/src/modules/modules.js @@ -10,7 +10,6 @@ export {default as DataStore} from "./datastore"; export {default as Events} from "./emitter"; export {default as Settings} from "./settingsmanager"; export {default as DOMManager} from "./dommanager"; -export {default as DOM} from "./domtools"; export {default as Patcher} from "./patcher"; export {default as LocaleManager} from "./localemanager"; export {default as Strings} from "./strings"; diff --git a/renderer/src/modules/pluginapi.js b/renderer/src/modules/pluginapi.js deleted file mode 100644 index 7cace1ee..00000000 --- a/renderer/src/modules/pluginapi.js +++ /dev/null @@ -1,708 +0,0 @@ -import {Config} from "data"; -import Utilities from "./utilities"; -import WebpackModules, {Filters} from "./webpackmodules"; -import DiscordModules from "./discordmodules"; -import DataStore from "./datastore"; -import DOMManager from "./dommanager"; -import Toasts from "../ui/toasts"; -import Notices from "../ui/notices"; -import Modals from "../ui/modals"; -import PluginManager from "./pluginmanager"; -import ThemeManager from "./thememanager"; -import Settings from "./settingsmanager"; -import Logger from "common/logger"; -import Patcher from "./patcher"; -import Emotes from "../builtins/emotes/emotes"; -import ipc from "./ipc"; -import Tooltip from "../ui/tooltip"; - -/** - * `BdApi` is a globally (`window.BdApi`) accessible object for use by plugins and developers to make their lives easier. - * @name BdApi - */ -const BdApi = { - /** - * The React module being used inside Discord. - * @type React - * */ - get React() {return DiscordModules.React;}, - - /** - * The ReactDOM module being used inside Discord. - * @type ReactDOM - */ - get ReactDOM() {return DiscordModules.ReactDOM;}, - - /** - * A reference object to get BD's settings. - * @type object - * @deprecated - */ - get settings() {return Settings.collections;}, - - /** - * A reference object for BD's emotes. - * @type object - * @deprecated - */ - get emotes() { - return new Proxy(Emotes.Emotes, { - get(obj, category) { - if (category === "blocklist") return Emotes.blocklist; - const group = Emotes.Emotes[category]; - if (!group) return undefined; - return new Proxy(group, { - get(cat, emote) {return group[emote];}, - set() {Logger.warn("BdApi.emotes", "Addon policy for plugins #5 https://github.com/BetterDiscord/BetterDiscord/wiki/Addon-Policies#plugins");} - }); - }, - set() {Logger.warn("BdApi.emotes", "Addon policy for plugins #5 https://github.com/BetterDiscord/BetterDiscord/wiki/Addon-Policies#plugins");} - }); - }, - - /** - * A reference string for BD's version. - * @type string - */ - get version() {return Config.version;} -}; - - -/** - * Adds a `