this repo has no description

Add contextMenu library

Changed files
+304
packages
core-extensions
src
contextMenu
types
+37
packages/core-extensions/src/contextMenu/index.tsx
···
···
+
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
+
+
export const patches: Patch[] = [
+
{
+
find: "Menu API only allows Items and groups of Items as children.",
+
replace: [
+
{
+
match:
+
/(?<=let{navId[^}]+?}=(.),(.)=function .\(.\){.+(?=,.=function))/,
+
replacement: (_, props, items) =>
+
`,__contextMenu=!${props}.__contextMenu_evilMenu&&require("contextMenu_contextMenu")._patchMenu(${props}, ${items})`
+
}
+
]
+
},
+
{
+
find: ".getContextMenu(",
+
replace: [
+
{
+
match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/,
+
replacement: (render) =>
+
`require("contextMenu_contextMenu")._saveProps(${render})`
+
}
+
]
+
}
+
];
+
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
+
contextMenu: {
+
dependencies: [{ ext: "spacepack", id: "spacepack" }, "MenuGroup:"]
+
},
+
evilMenu: {
+
dependencies: [
+
{ ext: "spacepack", id: "spacepack" },
+
"Menu API only allows Items and groups of Items as children."
+
]
+
}
+
};
+9
packages/core-extensions/src/contextMenu/manifest.json
···
···
+
{
+
"id": "contextMenu",
+
"meta": {
+
"name": "Context Menu",
+
"tagline": "A library for patching and creating context menus",
+
"authors": ["redstonekasi"],
+
"tags": ["library"]
+
}
+
}
+66
packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts
···
···
+
import {
+
InternalItem,
+
MenuElement,
+
MenuProps
+
} from "@moonlight-mod/types/coreExtensions/contextMenu";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import parser from "@moonlight-mod/wp/contextMenu_evilMenu";
+
+
type Patch = {
+
navId: string;
+
item: (
+
props: any
+
) =>
+
| React.ReactComponentElement<MenuElement>
+
| React.ReactComponentElement<MenuElement>[];
+
anchorId: string;
+
before: boolean;
+
};
+
+
export function addItem<T>(
+
navId: string,
+
item: (
+
props: T
+
) =>
+
| React.ReactComponentElement<MenuElement>
+
| React.ReactComponentElement<MenuElement>[],
+
anchorId: string,
+
before = false
+
) {
+
patches.push({ navId, item, anchorId, before });
+
}
+
+
export const patches: Patch[] = [];
+
function _patchMenu(props: MenuProps, items: InternalItem[]) {
+
const matches = patches.filter((p) => p.navId === props.navId);
+
if (!matches.length) return;
+
+
for (const patch of matches) {
+
const idx = items.findIndex((i) => i.key === patch.anchorId);
+
if (idx === -1) continue;
+
items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps)));
+
}
+
}
+
+
let menuProps: any;
+
function _saveProps(el: any) {
+
menuProps = el.props;
+
+
const original = el.props.config.onClose;
+
el.props.config.onClose = function (...args: any[]) {
+
menuProps = undefined;
+
return original?.apply(this, args);
+
};
+
+
return el;
+
}
+
+
const MenuElements = spacepack.findByCode("return null", "MenuGroup:")[0]
+
.exports;
+
+
module.exports = {
+
...MenuElements,
+
addItem,
+
_patchMenu,
+
_saveProps
+
};
+24
packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts
···
···
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
+
let code =
+
spacepack.require.m[
+
spacepack.findByCode(
+
"Menu API only allows Items and groups of Items as children."
+
)[0].id
+
].toString();
+
code = code.replace(/,.=(?=function .\(.\){.+?,.=function)/, ";return ");
+
code = code.replace(/,(?=__contextMenu)/, ";let ");
+
const mod = new Function(
+
"module",
+
"exports",
+
"require",
+
`(${code}).apply(this, arguments)`
+
);
+
const exp: any = {};
+
mod({}, exp, require);
+
module.exports = (el: any) => {
+
return exp.Menu({
+
children: el,
+
__contextMenu_evilMenu: true
+
});
+
};
+1
packages/types/src/coreExtensions.ts
···
export type CommonFluxDispatcher = Dispatcher<any>;
export * as Markdown from "./coreExtensions/markdown";
···
export type CommonFluxDispatcher = Dispatcher<any>;
export * as Markdown from "./coreExtensions/markdown";
+
export * as ContextMenu from "./coreExtensions/contextMenu";
+151
packages/types/src/coreExtensions/contextMenu.ts
···
···
+
/* eslint-disable prettier/prettier */
+
+
// TODO: Deduplicate common props
+
+
export type Menu = React.FunctionComponent<{
+
navId: string;
+
variant?: string;
+
hideScrollbar?: boolean;
+
className?: string;
+
children: React.ReactComponentElement<MenuElement>[];
+
onClose?: () => void;
+
onSelect?: () => void;
+
}>;
+
export type MenuProps = React.ComponentProps<Menu>;
+
+
export type MenuElement =
+
| MenuSeparator
+
| MenuGroup
+
| MenuItem
+
| MenuCheckboxItem
+
| MenuRadioItem
+
| MenuControlItem;
+
+
export type MenuSeparator = React.FunctionComponent;
+
export type MenuGroup = React.FunctionComponent<{
+
label?: string;
+
className?: string;
+
color?: string;
+
children: React.ReactComponentElement<MenuElement>[];
+
}>;
+
export type MenuItem = React.FunctionComponent<{
+
id: any;
+
dontCloseOnActionIfHoldingShiftKey?: boolean;
+
} & ({
+
label: string;
+
subtext?: string;
+
color?: string;
+
hint?: string;
+
disabled?: boolean;
+
icon?: any;
+
showIconFirst?: boolean;
+
imageUrl?: string;
+
+
className?: string;
+
focusedClassName?: string;
+
subMenuIconClassName?: string;
+
+
action?: () => void;
+
onFocus?: () => void;
+
+
iconProps?: any;
+
sparkle?: any;
+
+
children?: React.ReactComponentElement<MenuElement>[];
+
onChildrenScroll?: any;
+
childRowHeight?: any;
+
listClassName?: string;
+
subMenuClassName?: string;
+
} | {
+
color?: string;
+
disabled?: boolean;
+
keepItemStyles?: boolean;
+
+
action?: () => void;
+
+
render: any;
+
navigable?: boolean;
+
})>;
+
export type MenuCheckboxItem = React.FunctionComponent<{
+
id: any;
+
label: string;
+
subtext?: string;
+
color?: string;
+
className?: string;
+
focusedClassName?: string;
+
disabled?: boolean;
+
checked: boolean;
+
action?: () => void;
+
}>;
+
export type MenuRadioItem = React.FunctionComponent<{
+
id: any;
+
label: string;
+
subtext?: string;
+
color?: string;
+
disabled?: boolean;
+
action?: () => void;
+
}>;
+
export type MenuControlItem = React.FunctionComponent<{
+
id: any;
+
label: string;
+
color?: string;
+
disabled?: boolean;
+
showDefaultFocus?: boolean;
+
} & ({
+
control: any;
+
} | {
+
control?: undefined;
+
interactive?: boolean;
+
children?: React.ReactComponentElement<MenuElement>[];
+
})>;
+
+
export type ContextMenu = {
+
addItem: (
+
navId: string,
+
item: (props: any) => React.ReactComponentElement<MenuElement>,
+
anchorId: string,
+
before?: boolean
+
) => void;
+
+
MenuCheckboxItem: MenuCheckboxItem;
+
MenuControlItem: MenuControlItem;
+
MenuGroup: MenuGroup;
+
MenuItem: MenuItem;
+
MenuRadioItem: MenuRadioItem;
+
MenuSeparator: MenuSeparator;
+
};
+
+
export type InternalItem = {
+
type: string;
+
key?: string;
+
};
+
+
export type InternalSeparator = {
+
type: "separator";
+
navigable: false;
+
}
+
export type InternalGroupStart = {
+
type: "groupstart";
+
length: number;
+
navigable: false;
+
props: React.ComponentProps<MenuGroup>;
+
}
+
export type InternalGroupEnd = {
+
type: "groupend";
+
} & Omit<InternalGroupStart, "type">;
+
export type InternalCustomItem = {
+
type: "customitem";
+
key: any;
+
navigable?: boolean;
+
render: any;
+
props: Extract<React.ComponentProps<MenuItem>, { render: any }>;
+
}
+
export type InternalItem_ = {
+
type: "item";
+
key: any;
+
navigable: true;
+
label: string;
+
+
}
+
+
export type EvilItemParser = (el: React.ReactComponentElement<MenuElement> | React.ReactComponentElement<MenuElement>[]) => InternalItem[];
+4
packages/types/src/discord/require.ts
···
CommonComponents,
CommonFluxDispatcher
} from "../coreExtensions";
import { Markdown } from "../coreExtensions/markdown";
declare function WebpackRequire(id: string): any;
···
};
declare function WebpackRequire(id: "markdown_markdown"): Markdown;
export default WebpackRequire;
···
CommonComponents,
CommonFluxDispatcher
} from "../coreExtensions";
+
import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu";
import { Markdown } from "../coreExtensions/markdown";
declare function WebpackRequire(id: string): any;
···
};
declare function WebpackRequire(id: "markdown_markdown"): Markdown;
+
+
declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser;
+
declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu;
export default WebpackRequire;
+12
packages/types/src/import.d.ts
···
const Markdown: CoreExtensions.Markdown.Markdown;
export = Markdown;
}
···
const Markdown: CoreExtensions.Markdown.Markdown;
export = Markdown;
}
+
+
declare module "@moonlight-mod/wp/contextMenu_evilMenu" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
const EvilParser: CoreExtensions.ContextMenu.EvilItemParser;
+
export = EvilParser;
+
}
+
+
declare module "@moonlight-mod/wp/contextMenu_contextMenu" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
const ContextMenu: CoreExtensions.ContextMenu.ContextMenu;
+
export = ContextMenu;
+
}