From eaf1af75bd9df5fa70df2a4011d21c1e609519fb Mon Sep 17 00:00:00 2001 From: V Date: Sat, 8 Apr 2023 03:51:37 +0200 Subject: WebContextMenus: Port more menus (#818) Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- src/plugins/webContextMenus.web.ts | 218 +++++++++++++++++++++++++++++++++---- 1 file changed, 199 insertions(+), 19 deletions(-) (limited to 'src/plugins/webContextMenus.web.ts') diff --git a/src/plugins/webContextMenus.web.ts b/src/plugins/webContextMenus.web.ts index 56990e1..e1c9725 100644 --- a/src/plugins/webContextMenus.web.ts +++ b/src/plugins/webContextMenus.web.ts @@ -16,31 +16,211 @@ * along with this program. If not, see . */ +import { definePluginSettings } from "@api/settings"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; +import { saveFile } from "@utils/web"; +import { findByProps, findLazy } from "@webpack"; +import { Clipboard } from "@webpack/common"; + +async function fetchImage(url: string) { + const res = await fetch(url); + if (res.status !== 200) return; + + return await res.blob(); +} + +const MiniDispatcher = findLazy(m => m.emitter?._events?.INSERT_TEXT); + +const settings = definePluginSettings({ + // This needs to be all in one setting because to enable any of these, we need to make Discord use their desktop context + // menu handler instead of the web one, which breaks the other menus that aren't enabled + addBack: { + type: OptionType.BOOLEAN, + description: "Add back the Discord context menus for images, links and the chat input bar", + // Web slate menu has proper spellcheck suggestions and image context menu is also pretty good, + // so disable this by default. Vencord Desktop just doesn't, so enable by default + default: IS_VENCORD_DESKTOP, + restartNeeded: true + } +}); export default definePlugin({ name: "WebContextMenus", - description: "Re-adds some of context menu items missing on the web version of Discord, namely Copy/Open Link", + description: "Re-adds context menus missing in the web version of Discord: Images, ChatInputBar, Links, 'Copy Link', 'Open Link', 'Copy Image', 'Save Image'", authors: [Devs.Ven], enabledByDefault: true, - patches: [{ - // There is literally no reason for Discord to make this Desktop only. - // The only thing broken is copy, but they already have a different copy function - // with web support???? - find: "open-native-link", - replacement: [ - { - // if (isNative || null == - match: /if\(!\w\..{1,3}\|\|null==/, - replace: "if(null==" - }, - // Fix silly Discord calling the non web support copy - { - match: /\w\.default\.copy/, - replace: "Vencord.Webpack.Common.Clipboard.copy" + settings, + + start() { + if (settings.store.addBack) { + const ctxMenuCallbacks = findByProps("contextMenuCallbackNative"); + window.removeEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackWeb); + window.addEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackNative); + this.changedListeners = true; + } + }, + + stop() { + if (this.changedListeners) { + const ctxMenuCallbacks = findByProps("contextMenuCallbackNative"); + window.removeEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackNative); + window.addEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackWeb); + } + }, + + patches: [ + // Add back Copy & Open Link + { + // There is literally no reason for Discord to make this Desktop only. + // The only thing broken is copy, but they already have a different copy function + // with web support???? + find: "open-native-link", + replacement: [ + { + // if (IS_DESKTOP || null == ...) + match: /if\(!\i\.\i\|\|null==/, + replace: "if(null==" + }, + // Fix silly Discord calling the non web support copy + { + match: /\w\.default\.copy/, + replace: "Vencord.Webpack.Common.Clipboard.copy" + } + ] + }, + + // Add back Copy & Save Image + { + find: 'id:"copy-image"', + replacement: [ + { + // if (!IS_WEB || null == + match: /if\(!\i\.\i\|\|null==/, + replace: "if(null==" + }, + { + match: /return\s*?\[\i\.default\.canCopyImage\(\)/, + replace: "return [true" + }, + { + match: /(?<=COPY_IMAGE_MENU_ITEM,)action:/, + replace: "action:()=>$self.copyImage(arguments[0]),oldAction:" + }, + { + match: /(?<=SAVE_IMAGE_MENU_ITEM,)action:/, + replace: "action:()=>$self.saveImage(arguments[0]),oldAction:" + }, + ] + }, + + // Add back image context menu + { + find: 'navId:"image-context"', + predicate: () => settings.store.addBack, + replacement: { + // return IS_DESKTOP ? React.createElement(Menu, ...) + match: /return \i\.\i\?(?=\(0,\i\.jsxs?\)\(\i\.Menu)/, + replace: "return true?" + } + }, + + // Add back link context menu + { + find: '"interactionUsernameProfile"', + predicate: () => settings.store.addBack, + replacement: { + match: /if\("A"===\i\.tagName&&""!==\i\.textContent\)/, + replace: "if(false)" + } + }, + + // Add back slate / text input context menu + { + find: '"slate-toolbar"', + predicate: () => settings.store.addBack, + replacement: { + match: /(?<=\.handleContextMenu=.+?"bottom";)\i\.\i\?/, + replace: "true?" } - ] - }] + }, + { + find: 'navId:"textarea-context"', + predicate: () => settings.store.addBack, + replacement: [ + { + // desktopOnlyEntries = makeEntries(), spellcheckChildren = desktopOnlyEntries[0], languageChildren = desktopOnlyEntries[1] + match: /\i=.{0,30}text:\i,target:\i,onHeightUpdate:\i\}\),2\),(\i)=\i\[0\],(\i)=\i\[1\]/, + // set spellcheckChildren & languageChildren to empty arrays, so just in case patch 3 fails, we don't + // reference undefined variables + replace: "$1=[],$2=[]", + }, + { + // if (!IS_DESKTOP) return + match: /(?<=showApplicationCommandSuggestions;)if\(!\i\.\i\)/, + replace: "if(false)" + }, + { + // do not add menu items for entries removed in patch 1. Using a lookbehind for group 1 is slow, + // so just capture and add back + match: /("submit-button".+?)(\(0,\i\.jsx\)\(\i\.MenuGroup,\{children:\i\}\),){2}/, + replace: "$1" + }, + { + // Change calls to DiscordNative.clipboard to us instead + match: /\b\i\.default\.(copy|cut|paste)/g, + replace: "$self.$1" + } + ] + } + + // TODO: Maybe add spellcheck for VencordDesktop + ], + + async copyImage(url: string) { + const data = await fetchImage(url); + if (!data) return; + + await navigator.clipboard.write([ + new ClipboardItem({ + [data.type]: data + }) + ]); + }, + + async saveImage(url: string) { + const data = await fetchImage(url); + if (!data) return; + + const name = url.split("/").pop()!; + const file = new File([data], name, { type: data.type }); + + saveFile(file); + }, + + copy() { + const selection = document.getSelection(); + if (!selection) return; + + Clipboard.copy(selection.toString()); + }, + + cut() { + this.copy(); + MiniDispatcher.dispatch("INSERT_TEXT", { rawText: "" }); + }, + + async paste() { + const text = await navigator.clipboard.readText(); + + const data = new DataTransfer(); + data.setData("text/plain", text); + + document.dispatchEvent( + new ClipboardEvent("paste", { + clipboardData: data + }) + ); + } }); -- cgit