diff --git a/src/globals.d.ts b/src/globals.d.ts index f4dcaa9..90633f9 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -8,6 +8,7 @@ declare global { export var VencordDesktopNative: typeof import("preload/VencordDesktopNative").VencordDesktopNative; export var VencordDesktop: typeof import("renderer/index"); export var vcdLS: typeof localStorage; + export var VCDP: any; export var IS_DEV: boolean; } diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 138ad76..55f16ee 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -76,6 +76,14 @@ ipcMain.handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => { if (applicable.length) ses.setSpellCheckerLanguages(applicable); }); +ipcMain.handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => { + e.sender.replaceMisspelling(word); +}); + +ipcMain.handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => { + e.sender.session.addWordToSpellCheckerDictionary(word); +}); + ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => { const res = await dialog.showOpenDialog(mainWin!, { properties: ["openDirectory"] diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index 2f3b667..139ae5a 100644 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -6,6 +6,7 @@ import { app, BrowserWindow, BrowserWindowConstructorOptions, Menu, Tray } from "electron"; import { join } from "path"; +import { IpcEvents } from "shared/IpcEvents"; import { once } from "shared/utils/once"; import { ICON_PATH } from "../shared/paths"; @@ -216,6 +217,12 @@ function initSettingsListeners(win: BrowserWindow) { }); } +function initSpellCheck(win: BrowserWindow) { + win.webContents.on("context-menu", (_, data) => { + win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions); + }); +} + function createMainWindow() { const win = (mainWin = new BrowserWindow({ show: false, @@ -225,7 +232,8 @@ function createMainWindow() { sandbox: false, contextIsolation: true, devTools: true, - preload: join(__dirname, "preload.js") + preload: join(__dirname, "preload.js"), + spellcheck: true }, icon: ICON_PATH, frame: VencordSettings.store.frameless !== true, @@ -255,6 +263,7 @@ function createMainWindow() { initMenuBar(win); makeLinksOpenExternally(win); initSettingsListeners(win); + initSpellCheck(win); win.webContents.setUserAgent( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" diff --git a/src/preload/VencordDesktopNative.ts b/src/preload/VencordDesktopNative.ts index d526fa8..f0f7c9c 100644 --- a/src/preload/VencordDesktopNative.ts +++ b/src/preload/VencordDesktopNative.ts @@ -4,12 +4,21 @@ * Copyright (c) 2023 Vendicated and Vencord contributors */ +import { ipcRenderer } from "electron"; import type { Settings } from "shared/settings"; import type { LiteralUnion } from "type-fest"; import { IpcEvents } from "../shared/IpcEvents"; import { invoke, sendSync } from "./typedIpcs"; +type SpellCheckerResultCallback = (word: string, suggestions: string[]) => void; + +const spellCheckCallbacks = new Set(); + +ipcRenderer.on(IpcEvents.SPELLCHECK_RESULT, (_, w: string, s: string[]) => { + spellCheckCallbacks.forEach(cb => cb(w, s)); +}); + export const VencordDesktopNative = { app: { relaunch: () => invoke(IpcEvents.RELAUNCH), @@ -30,8 +39,15 @@ export const VencordDesktopNative = { set: (settings: Settings, path?: string) => invoke(IpcEvents.SET_SETTINGS, settings, path) }, spellcheck: { - setLanguages: (languages: readonly string[]) => invoke(IpcEvents.SPELLCHECK_SET_LANGUAGES, languages) - // todo: perhaps add ways to learn words + setLanguages: (languages: readonly string[]) => invoke(IpcEvents.SPELLCHECK_SET_LANGUAGES, languages), + onSpellcheckResult(cb: SpellCheckerResultCallback) { + spellCheckCallbacks.add(cb); + }, + offSpellcheckResult(cb: SpellCheckerResultCallback) { + spellCheckCallbacks.delete(cb); + }, + replaceMisspelling: (word: string) => invoke(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, word), + addToDictionary: (word: string) => invoke(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, word) }, win: { focus: () => invoke(IpcEvents.FOCUS) diff --git a/src/renderer/index.ts b/src/renderer/index.ts index d44f15b..ef8e93c 100644 --- a/src/renderer/index.ts +++ b/src/renderer/index.ts @@ -6,6 +6,7 @@ import "./fixes"; import "./appBadge"; +import "./patches"; console.log("read if cute :3"); diff --git a/src/renderer/patches/index.ts b/src/renderer/patches/index.ts new file mode 100644 index 0000000..997788a --- /dev/null +++ b/src/renderer/patches/index.ts @@ -0,0 +1,8 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2023 Vendicated and Vencord contributors + */ + +// TODO: Possibly auto generate glob if we have more patches in the future +import "./spellCheck"; diff --git a/src/renderer/patches/shared.ts b/src/renderer/patches/shared.ts new file mode 100644 index 0000000..6ec9d5d --- /dev/null +++ b/src/renderer/patches/shared.ts @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2023 Vendicated and Vencord contributors + */ + +import { Patch } from "@vencord/types/utils/types"; + +window.VCDP = {}; + +interface PatchData { + patches: Omit[]; + [key: string]: any; +} + +export function addPatch

(p: P) { + const { patches, ...globals } = p; + + for (const patch of patches as Patch[]) { + if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement]; + for (const r of patch.replacement) { + if (typeof r.replace === "string") r.replace = r.replace.replaceAll("$self", "VCDP"); + } + + patch.plugin = "VencordDesktop"; + Vencord.Plugins.patches.push(patch as Patch); + } + + Object.assign(VCDP, globals); +} diff --git a/src/renderer/patches/spellCheck.tsx b/src/renderer/patches/spellCheck.tsx new file mode 100644 index 0000000..9654bc2 --- /dev/null +++ b/src/renderer/patches/spellCheck.tsx @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2023 Vendicated and Vencord contributors + */ + +import { addContextMenuPatch } from "@vencord/types/api/ContextMenu"; +import { Menu } from "@vencord/types/webpack/common"; + +import { addPatch } from "./shared"; + +let word: string; +let corrections: string[]; + +// Make spellcheck suggestions work +addPatch({ + patches: [ + { + find: ".enableSpellCheck)", + replacement: { + // if (isDesktop) { DiscordNative.onSpellcheck(openMenu(props)) } else { e.preventDefault(); openMenu(props) } + match: /else\{.{1,3}\.preventDefault\(\);(.{1,3}\(.{1,3}\))\}/, + // ... else { $self.onSlateContext(() => openMenu(props)) } + replace: "else {$self.onSlateContext(() => $1)}" + } + } + ], + + onSlateContext(openMenu: () => void) { + const cb = (w: string, c: string[]) => { + VencordDesktopNative.spellcheck.offSpellcheckResult(cb); + word = w; + corrections = c; + openMenu(); + }; + VencordDesktopNative.spellcheck.onSpellcheckResult(cb); + } +}); + +addContextMenuPatch("textarea-context", children => () => { + if (!word || !corrections?.length) return; + + children.push( + + {corrections.map(c => ( + VencordDesktopNative.spellcheck.replaceMisspelling(c)} + /> + ))} + + VencordDesktopNative.spellcheck.addToDictionary(word)} + /> + + ); +}); diff --git a/src/shared/IpcEvents.ts b/src/shared/IpcEvents.ts index 2a6bf1d..ff129ec 100644 --- a/src/shared/IpcEvents.ts +++ b/src/shared/IpcEvents.ts @@ -27,6 +27,9 @@ export const enum IpcEvents { UPDATE_IGNORE = "VCD_UPDATE_IGNORE", SPELLCHECK_SET_LANGUAGES = "VCD_SPELLCHECK_SET_LANGUAGES", + SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT", + SPELLCHECK_REPLACE_MISSPELLING = "VCD_SPELLCHECK_REPLACE_MISSPELLING", + SPELLCHECK_ADD_TO_DICTIONARY = "VCD_SPELLCHECK_ADD_TO_DICTIONARY", SET_BADGE_COUNT = "VCD_SET_BADGE_COUNT",