Make Context Menu API support hooks (#902)
Co-authored-by: V <vendicated@riseup.net>
This commit is contained in:
parent
e8809fc57b
commit
96f640da67
10 changed files with 29 additions and 24 deletions
|
@ -19,17 +19,20 @@
|
||||||
import Logger from "@utils/Logger";
|
import Logger from "@utils/Logger";
|
||||||
import type { ReactElement } from "react";
|
import type { ReactElement } from "react";
|
||||||
|
|
||||||
|
type ContextMenuPatchCallbackReturn = (() => void) | void;
|
||||||
/**
|
/**
|
||||||
* @param children The rendered context menu elements
|
* @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
|
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||||
|
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
|
||||||
*/
|
*/
|
||||||
export type NavContextMenuPatchCallback = (children: Array<React.ReactElement>, ...args: Array<any>) => void;
|
export type NavContextMenuPatchCallback = (children: Array<React.ReactElement>, ...args: Array<any>) => ContextMenuPatchCallbackReturn;
|
||||||
/**
|
/**
|
||||||
* @param The navId of the context menu being patched
|
* @param navId The navId of the context menu being patched
|
||||||
* @param children The rendered context menu elements
|
* @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
|
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||||
|
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
|
||||||
*/
|
*/
|
||||||
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<React.ReactElement>, ...args: Array<any>) => void;
|
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<React.ReactElement>, ...args: Array<any>) => ContextMenuPatchCallbackReturn;
|
||||||
|
|
||||||
const ContextMenuLogger = new Logger("ContextMenu");
|
const ContextMenuLogger = new Logger("ContextMenu");
|
||||||
|
|
||||||
|
@ -78,6 +81,7 @@ export function removeContextMenuPatch<T extends string | Array<string>>(navId:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a global context menu patch
|
* Remove a global context menu patch
|
||||||
|
* @param patch The patch to be removed
|
||||||
* @returns Wheter the patch was sucessfully removed
|
* @returns Wheter the patch was sucessfully removed
|
||||||
*/
|
*/
|
||||||
export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback): boolean {
|
export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback): boolean {
|
||||||
|
@ -87,12 +91,13 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
|
||||||
/**
|
/**
|
||||||
* 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
|
* 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
|
* @param id The id of the child
|
||||||
|
* @param children The context menu children
|
||||||
*/
|
*/
|
||||||
export function findGroupChildrenByChildId(id: string, children: Array<React.ReactElement>, itemsArray?: Array<React.ReactElement>): Array<React.ReactElement> | null {
|
export function findGroupChildrenByChildId(id: string, children: Array<React.ReactElement>, _itemsArray?: Array<React.ReactElement>): Array<React.ReactElement> | null {
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (child == null) continue;
|
if (child == null) continue;
|
||||||
|
|
||||||
if (child.props?.id === id) return itemsArray ?? null;
|
if (child.props?.id === id) return _itemsArray ?? null;
|
||||||
|
|
||||||
let nextChildren = child.props?.children;
|
let nextChildren = child.props?.children;
|
||||||
if (nextChildren) {
|
if (nextChildren) {
|
||||||
|
@ -121,9 +126,6 @@ interface ContextMenuProps {
|
||||||
const patchedMenus = new WeakSet();
|
const patchedMenus = new WeakSet();
|
||||||
|
|
||||||
export function _patchContextMenu(props: ContextMenuProps) {
|
export function _patchContextMenu(props: ContextMenuProps) {
|
||||||
if (patchedMenus.has(props)) return;
|
|
||||||
patchedMenus.add(props);
|
|
||||||
|
|
||||||
props.contextMenuApiArguments ??= [];
|
props.contextMenuApiArguments ??= [];
|
||||||
const contextMenuPatches = navPatches.get(props.navId);
|
const contextMenuPatches = navPatches.get(props.navId);
|
||||||
|
|
||||||
|
@ -132,7 +134,8 @@ export function _patchContextMenu(props: ContextMenuProps) {
|
||||||
if (contextMenuPatches) {
|
if (contextMenuPatches) {
|
||||||
for (const patch of contextMenuPatches) {
|
for (const patch of contextMenuPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.children, ...props.contextMenuApiArguments);
|
const callback = patch(props.children, ...props.contextMenuApiArguments);
|
||||||
|
if (!patchedMenus.has(props)) callback?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
||||||
}
|
}
|
||||||
|
@ -141,9 +144,12 @@ export function _patchContextMenu(props: ContextMenuProps) {
|
||||||
|
|
||||||
for (const patch of globalPatches) {
|
for (const patch of globalPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
const callback = patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
||||||
|
if (!patchedMenus.has(props)) callback?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error("Global patch errored,", err);
|
ContextMenuLogger.error("Global patch errored,", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patchedMenus.add(props);
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,8 +238,8 @@ function initWs(isManual = false) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextMenuPatch: NavContextMenuPatchCallback = kids => {
|
const contextMenuPatch: NavContextMenuPatchCallback = children => () => {
|
||||||
kids.unshift(
|
children.unshift(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id={NAV_ID}
|
id={NAV_ID}
|
||||||
label="Reconnect Dev Companion"
|
label="Reconnect Dev Companion"
|
||||||
|
|
|
@ -210,7 +210,7 @@ function isGifUrl(url: string) {
|
||||||
return new URL(url).pathname.endsWith(".gif");
|
return new URL(url).pathname.endsWith(".gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
||||||
const { favoriteableId, itemHref, itemSrc, favoriteableType } = props ?? {};
|
const { favoriteableId, itemHref, itemSrc, favoriteableType } = props ?? {};
|
||||||
|
|
||||||
if (!favoriteableId || favoriteableType !== "emoji") return;
|
if (!favoriteableId || favoriteableType !== "emoji") return;
|
||||||
|
@ -223,7 +223,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =
|
||||||
if (group) group.push(buildMenuItem(favoriteableId, name, isGifUrl(itemHref ?? itemSrc)));
|
if (group) group.push(buildMenuItem(favoriteableId, name, isGifUrl(itemHref ?? itemSrc)));
|
||||||
};
|
};
|
||||||
|
|
||||||
const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => {
|
const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => () => {
|
||||||
const { id, name, type } = props?.target?.dataset ?? {};
|
const { id, name, type } = props?.target?.dataset ?? {};
|
||||||
if (!id || !name || type !== "emoji") return;
|
if (!id || !name || type !== "emoji") return;
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ export const settings = definePluginSettings({
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, _) => {
|
const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
|
||||||
children.push(
|
children.push(
|
||||||
<Menu.MenuGroup id="image-zoom">
|
<Menu.MenuGroup id="image-zoom">
|
||||||
{/* thanks SpotifyControls */}
|
{/* thanks SpotifyControls */}
|
||||||
|
|
|
@ -44,7 +44,7 @@ function addDeleteStyle() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MENU_ITEM_ID = "message-logger-remove-history";
|
const MENU_ITEM_ID = "message-logger-remove-history";
|
||||||
const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) => {
|
const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) => () => {
|
||||||
const { message } = props;
|
const { message } = props;
|
||||||
const { deleted, editHistory, id, channel_id } = message;
|
const { deleted, editHistory, id, channel_id } = message;
|
||||||
|
|
||||||
|
|
|
@ -49,13 +49,13 @@ function PinMenuItem(channelId: string) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupDMContext: NavContextMenuPatchCallback = (children, props) => {
|
const GroupDMContext: NavContextMenuPatchCallback = (children, props) => () => {
|
||||||
const container = findGroupChildrenByChildId("leave-channel", children);
|
const container = findGroupChildrenByChildId("leave-channel", children);
|
||||||
if (container)
|
if (container)
|
||||||
container.unshift(PinMenuItem(props.channel.id));
|
container.unshift(PinMenuItem(props.channel.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserContext: NavContextMenuPatchCallback = (children, props) => {
|
const UserContext: NavContextMenuPatchCallback = (children, props) => () => {
|
||||||
const container = findGroupChildrenByChildId("close-dm", children);
|
const container = findGroupChildrenByChildId("close-dm", children);
|
||||||
if (container) {
|
if (container) {
|
||||||
const idx = container.findIndex(c => c?.props?.id === "close-dm");
|
const idx = container.findIndex(c => c?.props?.id === "close-dm");
|
||||||
|
|
|
@ -34,7 +34,7 @@ function search(src: string, engine: string) {
|
||||||
open(engine + encodeURIComponent(src), "_blank");
|
open(engine + encodeURIComponent(src), "_blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
||||||
if (!props) return;
|
if (!props) return;
|
||||||
const { reverseImageSearchType, itemHref, itemSrc } = props;
|
const { reverseImageSearchType, itemHref, itemSrc } = props;
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ const ReplyIcon = LazyComponent(() => findByCode("M10 8.26667V4L3 11.4667L10 18.
|
||||||
|
|
||||||
const replyFn = findByCodeLazy("showMentionToggle", "TEXTAREA_FOCUS", "shiftKey");
|
const replyFn = findByCodeLazy("showMentionToggle", "TEXTAREA_FOCUS", "shiftKey");
|
||||||
|
|
||||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => {
|
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => () => {
|
||||||
// make sure the message is in the selected channel
|
// make sure the message is in the selected channel
|
||||||
if (SelectedChannelStore.getChannelId() !== message.channel_id) return;
|
if (SelectedChannelStore.getChannelId() !== message.channel_id) return;
|
||||||
|
|
||||||
|
@ -61,7 +61,6 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default definePlugin({
|
||||||
// doesn't contain our sections. This patches the actions of our
|
// doesn't contain our sections. This patches the actions of our
|
||||||
// sections to manually use SettingsRouter (which only works on desktop
|
// sections to manually use SettingsRouter (which only works on desktop
|
||||||
// but the context menu is usually not available on mobile anyway)
|
// but the context menu is usually not available on mobile anyway)
|
||||||
addContextMenuPatch("user-settings-cog", children => {
|
addContextMenuPatch("user-settings-cog", children => () => {
|
||||||
const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
|
const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
|
||||||
section?.forEach(c => {
|
section?.forEach(c => {
|
||||||
if (c?.props?.id?.startsWith("Vencord")) {
|
if (c?.props?.id?.startsWith("Vencord")) {
|
||||||
|
|
|
@ -81,7 +81,7 @@ function openImage(url: string) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => {
|
const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => () => {
|
||||||
const memberAvatar = GuildMemberStore.getMember(guildId!, user.id)?.avatar || null;
|
const memberAvatar = GuildMemberStore.getMember(guildId!, user.id)?.avatar || null;
|
||||||
|
|
||||||
children.splice(1, 0, (
|
children.splice(1, 0, (
|
||||||
|
@ -106,7 +106,7 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
const GuildContext: NavContextMenuPatchCallback = (children, { guild: { id, icon, banner } }: GuildContextProps) => {
|
const GuildContext: NavContextMenuPatchCallback = (children, { guild: { id, icon, banner } }: GuildContextProps) => () => {
|
||||||
if (!banner && !icon) return;
|
if (!banner && !icon) return;
|
||||||
|
|
||||||
// before copy id (if it exists)
|
// before copy id (if it exists)
|
||||||
|
|
Loading…
Reference in a new issue