diff options
Diffstat (limited to 'src/api')
-rw-r--r-- | src/api/ContextMenu.ts | 141 | ||||
-rw-r--r-- | src/api/index.ts | 6 |
2 files changed, 147 insertions, 0 deletions
diff --git a/src/api/ContextMenu.ts b/src/api/ContextMenu.ts new file mode 100644 index 0000000..6467117 --- /dev/null +++ b/src/api/ContextMenu.ts @@ -0,0 +1,141 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +import Logger from "@utils/Logger"; +import type { ReactElement } from "react"; + +/** + * @param children The rendered context menu elements + * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example + */ +export type NavContextMenuPatchCallback = (children: Array<React.ReactElement>, args?: Array<any>) => void; +/** + * @param The navId of the context menu being patched + * @param children The rendered context menu elements + * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example + */ +export type GlobalContextMenuPatchCallback = (navId: string, children: Array<React.ReactElement>, args?: Array<any>) => void; + +const ContextMenuLogger = new Logger("ContextMenu"); + +export const navPatches = new Map<string, Set<NavContextMenuPatchCallback>>(); +export const globalPatches = new Set<GlobalContextMenuPatchCallback>(); + +/** + * Add a context menu patch + * @param navId The navId(s) for the context menu(s) to patch + * @param patch The patch to be applied + */ +export function addContextMenuPatch(navId: string | Array<string>, patch: NavContextMenuPatchCallback) { + if (!Array.isArray(navId)) navId = [navId]; + for (const id of navId) { + let contextMenuPatches = navPatches.get(id); + if (!contextMenuPatches) { + contextMenuPatches = new Set(); + navPatches.set(id, contextMenuPatches); + } + + contextMenuPatches.add(patch); + } +} + +/** + * Add a global context menu patch that fires the patch for all context menus + * @param patch The patch to be applied + */ +export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback) { + globalPatches.add(patch); +} + +/** + * Remove a context menu patch + * @param navId The navId(s) for the context menu(s) to remove the patch + * @param patch The patch to be removed + * @returns Wheter the patch was sucessfully removed from the context menu(s) + */ +export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> { + const navIds = Array.isArray(navId) ? navId : [navId as string]; + + const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false); + + return (Array.isArray(navId) ? results : results[0]) as T extends string ? boolean : Array<boolean>; +} + +/** + * Remove a global context menu patch + * @returns Wheter the patch was sucessfully removed + */ +export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback): boolean { + return globalPatches.delete(patch); +} + +/** + * A helper function for finding the children array of a group nested inside a context menu based on the id of one of its childs + * @param id The id of the child + */ +export function findGroupChildrenByChildId(id: string, children: Array<React.ReactElement>, itemsArray?: Array<React.ReactElement>): Array<React.ReactElement> | null { + for (const child of children) { + if (child === null) continue; + + if (child.props?.id === id) return itemsArray ?? null; + + let nextChildren = child.props?.children; + if (nextChildren) { + if (!Array.isArray(nextChildren)) { + nextChildren = [nextChildren]; + child.props.children = nextChildren; + } + + const found = findGroupChildrenByChildId(id, nextChildren, nextChildren); + if (found !== null) return found; + } + } + + return null; +} + +interface ContextMenuProps { + contextMenuApiArguments?: Array<any>; + navId: string; + children: Array<ReactElement>; + "aria-label": string; + onSelect: (() => void) | undefined; + onClose: (callback: (...args: Array<any>) => any) => void; +} + +export function _patchContextMenu(props: ContextMenuProps) { + const contextMenuPatches = navPatches.get(props.navId); + + if (contextMenuPatches) { + for (const patch of contextMenuPatches) { + try { + patch(props.children, props.contextMenuApiArguments); + } catch (err) { + ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err); + } + } + } + + for (const patch of globalPatches) { + try { + patch(props.navId, props.children, props.contextMenuApiArguments); + } catch (err) { + ContextMenuLogger.error("Global patch errored,", err); + } + } +} diff --git a/src/api/index.ts b/src/api/index.ts index abb5093..e4b87bf 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -18,6 +18,7 @@ import * as $Badges from "./Badges"; import * as $Commands from "./Commands"; +import * as $ContextMenu from "./ContextMenu"; import * as $DataStore from "./DataStore"; import * as $MemberListDecorators from "./MemberListDecorators"; import * as $MessageAccessories from "./MessageAccessories"; @@ -93,3 +94,8 @@ export const Styles = $Styles; * An API allowing you to display notifications */ export const Notifications = $Notifications; + +/** + * An api allowing you to patch and add/remove items to/from context menus + */ +export const ContextMenu = $ContextMenu; |