diff --git a/package.json b/package.json index 22b99f8bc..65a2f4512 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.9.7", + "version": "1.9.9", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { @@ -70,6 +70,7 @@ "stylelint": "^16.8.1", "stylelint-config-standard": "^36.0.1", "ts-patch": "^3.2.1", + "ts-pattern": "^5.3.1", "tsx": "^4.16.5", "type-fest": "^4.23.0", "typescript": "^5.5.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9567475fb..eaa6b537c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,6 +116,9 @@ importers: ts-patch: specifier: ^3.2.1 version: 3.2.1 + ts-pattern: + specifier: ^5.3.1 + version: 5.3.1 tsx: specifier: ^4.16.5 version: 4.16.5 @@ -2524,6 +2527,9 @@ packages: resolution: {integrity: sha512-hlR43v+GUIUy8/ZGFP1DquEqPh7PFKQdDMTAmYt671kCCA6AkDQMoeFaFmZ7ObPLYOmpMgyKUqL1C+coFMf30w==} hasBin: true + ts-pattern@5.3.1: + resolution: {integrity: sha512-1RUMKa8jYQdNfmnK4jyzBK3/PS/tnjcZ1CW0v1vWDeYe5RBklc/nquw03MEoB66hVBm4BnlCfmOqDVxHyT1DpA==} + tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -5158,6 +5164,8 @@ snapshots: semver: 7.6.3 strip-ansi: 6.0.1 + ts-pattern@5.3.1: {} + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 817c2cec3..623f9f940 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -21,7 +21,7 @@ import esbuild from "esbuild"; import { readdir } from "fs/promises"; import { join } from "path"; -import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, watch } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch } from "./common.mjs"; const defines = { IS_STANDALONE, @@ -131,7 +131,7 @@ await Promise.all([ sourcemap, plugins: [ globPlugins("discordDesktop"), - ...commonOpts.plugins + ...commonRendererPlugins ], define: { ...defines, @@ -180,7 +180,7 @@ await Promise.all([ sourcemap, plugins: [ globPlugins("vencordDesktop"), - ...commonOpts.plugins + ...commonRendererPlugins ], define: { ...defines, diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index bc15ccced..deab86610 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -23,7 +23,7 @@ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises import { join } from "path"; import Zip from "zip-local"; -import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins } from "./common.mjs"; /** * @type {esbuild.BuildOptions} @@ -36,7 +36,7 @@ const commonOptions = { external: ["~plugins", "~git-hash", "/assets/*"], plugins: [ globPlugins("web"), - ...commonOpts.plugins, + ...commonRendererPlugins ], target: ["esnext"], define: { @@ -116,7 +116,12 @@ await Promise.all( } }) ] -); +).catch(err => { + console.error("Build failed"); + console.error(err.message); + if (!commonOpts.watch) + process.exit(1); +});; /** * @type {(dir: string) => Promise} diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index c46a559a7..e88f1e2b9 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -28,6 +28,7 @@ import { join, relative } from "path"; import { promisify } from "util"; import { getPluginTarget } from "../utils.mjs"; +import { builtinModules } from "module"; /** @type {import("../../package.json")} */ const PackageJSON = JSON.parse(readFileSync("package.json")); @@ -292,6 +293,18 @@ export const stylePlugin = { } }; +/** + * @type {(filter: RegExp, message: string) => import("esbuild").Plugin} + */ +export const banImportPlugin = (filter, message) => ({ + name: "ban-imports", + setup: build => { + build.onResolve({ filter }, () => { + return { errors: [{ text: message }] }; + }); + } +}); + /** * @type {import("esbuild").BuildOptions} */ @@ -311,3 +324,16 @@ export const commonOpts = { // Work around https://github.com/evanw/esbuild/issues/2460 tsconfig: "./scripts/build/tsconfig.esbuild.json" }; + +const escapedBuiltinModules = builtinModules + .map(m => m.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")) + .join("|"); +const builtinModuleRegex = new RegExp(`^(node:)?(${escapedBuiltinModules})$`); + +export const commonRendererPlugins = [ + banImportPlugin(builtinModuleRegex, "Cannot import node inbuilt modules in browser code. You need to use a native.ts file"), + banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"), + banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"), + banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"), + ...commonOpts.plugins +]; diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 88337a917..ac116f547 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -230,6 +230,10 @@ export function definePluginSettings< if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized"); return Settings.plugins[definedSettings.pluginName] as any; }, + get plain() { + if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized"); + return PlainSettings.plugins[definedSettings.pluginName] as any; + }, use: settings => useSettings( settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings[] ).plugins[definedSettings.pluginName] as any, diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 7ba078d33..fa142a18c 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -65,8 +65,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) { } /** - * Discord's copy icon, as seen in the user popout right of the username when clicking - * your own username in the bottom left user panel + * Discord's copy icon, as seen in the user panel popout on the right of the username and in large code blocks */ export function CopyIcon(props: IconProps) { return ( @@ -76,8 +75,9 @@ export function CopyIcon(props: IconProps) { viewBox="0 0 24 24" > - - + + + ); diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx index e09a1dbf3..fd33c09df 100644 --- a/src/components/VencordSettings/PatchHelperTab.tsx +++ b/src/components/VencordSettings/PatchHelperTab.tsx @@ -382,6 +382,7 @@ function PatchHelper() { Code + )} diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index aa8761d76..f718ab11f 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -25,10 +25,9 @@ import { openPluginModal } from "@components/PluginSettings/PluginModal"; import type { UserThemeHeader } from "@main/themes"; import { openInviteModal } from "@utils/discord"; import { Margins } from "@utils/margins"; -import { classes } from "@utils/misc"; import { showItemInFolder } from "@utils/native"; import { useAwaiter } from "@utils/react"; -import { findByPropsLazy, findLazy } from "@webpack"; +import { findLazy } from "@webpack"; import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common"; import type { ComponentType, Ref, SyntheticEvent } from "react"; @@ -45,9 +44,7 @@ type FileInput = ComponentType<{ filters?: { name?: string; extensions: string[]; }[]; }>; -const InviteActions = findByPropsLazy("resolveInvite"); const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef); -const TextAreaProps = findLazy(m => typeof m.textarea === "string"); const cl = classNameFactory("vc-settings-theme-"); @@ -80,8 +77,16 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) { Validator This section will tell you whether your themes can successfully be loaded
- {themeLinks.map(link => ( - { + const { label, link } = (() => { + const match = /^@(light|dark) (.*)/.exec(rawLink); + if (!match) return { label: rawLink, link: rawLink }; + + const [, mode, link] = match; + return { label: `[${mode} mode only] ${link}`, link }; + })(); + + return - {link} + {label} - - ))} + ; + })}
); @@ -299,6 +304,7 @@ function ThemesTab() { Paste links to css files here One link per line + You can prefix lines with @light or @dark to toggle them based on your Discord theme Make sure to use direct links to files (raw or github.io)! @@ -306,7 +312,7 @@ function ThemesTab() {