aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--browser/content.js17
-rw-r--r--browser/manifestv2.json2
-rw-r--r--browser/manifestv3.json2
-rw-r--r--package.json11
-rw-r--r--[-rwxr-xr-x]scripts/build/buildWeb.mjs74
-rw-r--r--scripts/build/common.mjs41
-rw-r--r--scripts/build/module/style.js26
-rw-r--r--src/Vencord.ts1
-rw-r--r--src/api/Styles.ts162
-rw-r--r--src/api/index.ts28
-rw-r--r--src/components/VencordSettings/index.tsx8
-rw-r--r--src/globals.d.ts6
-rw-r--r--src/ipcMain/index.ts1
-rw-r--r--src/ipcMain/legacy.ts31
-rw-r--r--src/ipcMain/updater/common.ts2
-rw-r--r--src/ipcMain/updater/http.ts2
-rw-r--r--src/modules.d.ts6
-rw-r--r--src/plugins/messageLogger/index.tsx39
-rw-r--r--src/plugins/messageLogger/messageLogger.css27
-rw-r--r--src/plugins/shikiCodeblocks/components/Header.tsx2
-rw-r--r--src/plugins/shikiCodeblocks/components/Highlighter.tsx6
-rw-r--r--src/plugins/shikiCodeblocks/devicon.css1
-rw-r--r--src/plugins/shikiCodeblocks/index.ts17
-rw-r--r--src/plugins/shikiCodeblocks/shiki.css4
-rw-r--r--src/plugins/shikiCodeblocks/utils/misc.ts3
-rw-r--r--src/plugins/spotifyControls/PlayerComponent.tsx2
-rw-r--r--src/plugins/spotifyControls/SpotifyStore.ts7
-rw-r--r--src/preload.ts28
-rw-r--r--src/utils/IpcEvents.ts3
-rw-r--r--src/utils/updater.ts2
-rw-r--r--src/webpack/common.tsx3
31 files changed, 438 insertions, 126 deletions
diff --git a/browser/content.js b/browser/content.js
index 2c4b40e..e47ef83 100644
--- a/browser/content.js
+++ b/browser/content.js
@@ -2,7 +2,18 @@ if (typeof browser === "undefined") {
var browser = chrome;
}
-var script = document.createElement("script");
+const script = document.createElement("script");
script.src = browser.runtime.getURL("dist/Vencord.js");
-// documentElement because we load before body/head are ready
-document.documentElement.appendChild(script);
+
+const style = document.createElement("link");
+style.type = "text/css";
+style.rel = "stylesheet";
+style.href = browser.runtime.getURL("dist/Vencord.css");
+
+document.documentElement.append(script);
+
+document.addEventListener(
+ "DOMContentLoaded",
+ () => document.documentElement.append(style),
+ { once: true }
+);
diff --git a/browser/manifestv2.json b/browser/manifestv2.json
index 405b2dc..b28b73f 100644
--- a/browser/manifestv2.json
+++ b/browser/manifestv2.json
@@ -18,7 +18,7 @@
"js": ["content.js"]
}
],
- "web_accessible_resources": ["dist/Vencord.js"],
+ "web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"],
"background": {
"scripts": ["background.js"]
}
diff --git a/browser/manifestv3.json b/browser/manifestv3.json
index ea79d12..d15b80a 100644
--- a/browser/manifestv3.json
+++ b/browser/manifestv3.json
@@ -23,7 +23,7 @@
"web_accessible_resources": [
{
- "resources": ["dist/Vencord.js"],
+ "resources": ["dist/Vencord.js", "dist/Vencord.css"],
"matches": ["*://*.discord.com/*"]
}
],
diff --git a/package.json b/package.json
index 35c4aaf..51d384d 100644
--- a/package.json
+++ b/package.json
@@ -65,6 +65,17 @@
"patchedDependencies": {
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
"eslint@8.28.0": "patches/eslint@8.28.0.patch"
+ },
+ "peerDependencyRules": {
+ "ignoreMissing": [
+ "eslint-plugin-import"
+ ]
+ },
+ "allowedDeprecatedVersions": {
+ "source-map-resolve": "*",
+ "resolve-url": "*",
+ "source-map-url": "*",
+ "urix": "*"
}
},
"webExt": {
diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs
index c85d8aa..3ad43b2 100755..100644
--- a/scripts/build/buildWeb.mjs
+++ b/scripts/build/buildWeb.mjs
@@ -20,13 +20,13 @@
import esbuild from "esbuild";
import { zip } from "fflate";
-import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
-import { readFile } from "fs/promises";
-import { join, resolve } from "path";
+import { readFileSync } from "fs";
+import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
+import { join } from "path";
// wtf is this assert syntax
import PackageJSON from "../../package.json" assert { type: "json" };
-import { commonOpts, fileIncludePlugin, gitHashPlugin, gitRemotePlugin, globPlugins, watch } from "./common.mjs";
+import { commonOpts, globPlugins, watch } from "./common.mjs";
/**
* @type {esbuild.BuildOptions}
@@ -39,9 +39,7 @@ const commonOptions = {
external: ["plugins", "git-hash"],
plugins: [
globPlugins,
- gitHashPlugin,
- gitRemotePlugin,
- fileIncludePlugin
+ ...commonOpts.plugins,
],
target: ["esnext"],
define: {
@@ -77,9 +75,13 @@ await Promise.all(
]
);
+/**
+ * @type {(target: string, files: string[], shouldZip: boolean) => Promise<void>}
+ */
async function buildPluginZip(target, files, shouldZip) {
const entries = {
- "dist/Vencord.js": readFileSync("dist/browser.js"),
+ "dist/Vencord.js": await readFile("dist/browser.js"),
+ "dist/Vencord.css": await readFile("dist/browser.css"),
...Object.fromEntries(await Promise.all(files.map(async f => [
(f.startsWith("manifest") ? "manifest.json" : f),
await readFile(join("browser", f))
@@ -87,29 +89,47 @@ async function buildPluginZip(target, files, shouldZip) {
};
if (shouldZip) {
- zip(entries, {}, (err, data) => {
- if (err) {
- console.error(err);
- process.exitCode = 1;
- } else {
- writeFileSync("dist/" + target, data);
- console.info("Extension written to dist/" + target);
- }
+ return new Promise((resolve, reject) => {
+ zip(entries, {}, (err, data) => {
+ if (err) {
+ reject(err);
+ } else {
+ const out = join("dist", target);
+ writeFile(out, data).then(() => {
+ console.info("Extension written to " + out);
+ resolve();
+ }).catch(reject);
+ }
+ });
});
} else {
- if (existsSync(target))
- rmSync(target, { recursive: true });
- for (const entry in entries) {
- const destination = "dist/" + target + "/" + entry;
- const parentDirectory = resolve(destination, "..");
- mkdirSync(parentDirectory, { recursive: true });
- writeFileSync(destination, entries[entry]);
- }
+ await rm(target, { recursive: true, force: true });
+ await Promise.all(Object.entries(entries).map(async ([file, content]) => {
+ const dest = join("dist", target, file);
+ const parentDirectory = join(dest, "..");
+ await mkdir(parentDirectory, { recursive: true });
+ await writeFile(dest, content);
+ }));
+
console.info("Unpacked Extension written to dist/" + target);
}
}
-await buildPluginZip("extension-v3.zip", ["modifyResponseHeaders.json", "content.js", "manifestv3.json"], true);
-await buildPluginZip("extension-v2.zip", ["background.js", "content.js", "manifestv2.json"], true);
-await buildPluginZip("extension-v2-unpacked", ["background.js", "content.js", "manifestv2.json"], false);
+const cssText = "`" + readFileSync("dist/Vencord.user.css", "utf-8").replaceAll("`", "\\`") + "`";
+const cssRuntime = `
+;document.addEventListener("DOMContentLoaded", () => document.documentElement.appendChild(
+ Object.assign(document.createElement("style"), {
+ textContent: ${cssText},
+ id: "vencord-css-core"
+ }),
+ { once: true }
+));
+`;
+
+await Promise.all([
+ appendFile("dist/Vencord.user.js", cssRuntime),
+ buildPluginZip("extension-v3.zip", ["modifyResponseHeaders.json", "content.js", "manifestv3.json"], true),
+ buildPluginZip("extension-v2.zip", ["background.js", "content.js", "manifestv2.json"], true),
+ buildPluginZip("extension-v2-unpacked", ["background.js", "content.js", "manifestv2.json"], false),
+]);
diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs
index 11aaa81..2743c70 100644
--- a/scripts/build/common.mjs
+++ b/scripts/build/common.mjs
@@ -17,9 +17,9 @@
*/
import { exec, execSync } from "child_process";
-import { existsSync } from "fs";
+import { existsSync, readFileSync } from "fs";
import { readdir, readFile } from "fs/promises";
-import { join } from "path";
+import { join, relative } from "path";
import { promisify } from "util";
export const watch = process.argv.includes("--watch");
@@ -35,7 +35,7 @@ export const banner = {
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
/**
- * @type {esbuild.Plugin}
+ * @type {import("esbuild").Plugin}
*/
export const makeAllPackagesExternalPlugin = {
name: "make-all-packages-external",
@@ -46,7 +46,7 @@ export const makeAllPackagesExternalPlugin = {
};
/**
- * @type {esbuild.Plugin}
+ * @type {import("esbuild").Plugin}
*/
export const globPlugins = {
name: "glob-plugins",
@@ -87,7 +87,7 @@ export const globPlugins = {
};
/**
- * @type {esbuild.Plugin}
+ * @type {import("esbuild").Plugin}
*/
export const gitHashPlugin = {
name: "git-hash-plugin",
@@ -103,7 +103,7 @@ export const gitHashPlugin = {
};
/**
- * @type {esbuild.Plugin}
+ * @type {import("esbuild").Plugin}
*/
export const gitRemotePlugin = {
name: "git-remote-plugin",
@@ -125,7 +125,7 @@ export const gitRemotePlugin = {
};
/**
- * @type {esbuild.Plugin}
+ * @type {import("esbuild").Plugin}
*/
export const fileIncludePlugin = {
name: "file-include-plugin",
@@ -147,6 +147,31 @@ export const fileIncludePlugin = {
}
};
+const styleModule = readFileSync("./scripts/build/module/style.js", "utf-8");
+/**
+ * @type {import("esbuild").Plugin}
+ */
+export const stylePlugin = {
+ name: "style-plugin",
+ setup: ({ onResolve, onLoad }) => {
+ onResolve({ filter: /\.css\?managed$/, namespace: "file" }, ({ path, resolveDir }) => ({
+ path: relative(process.cwd(), join(resolveDir, path.replace("?managed", ""))),
+ namespace: "managed-style",
+ }));
+ onLoad({ filter: /\.css$/, namespace: "managed-style" }, async ({ path }) => {
+ const css = await readFile(path, "utf-8");
+ const name = relative(process.cwd(), path).replaceAll("\\", "/");
+
+ return {
+ loader: "js",
+ contents: styleModule
+ .replaceAll("STYLE_SOURCE", JSON.stringify(css))
+ .replaceAll("STYLE_NAME", JSON.stringify(name))
+ };
+ });
+ }
+};
+
/**
* @type {import("esbuild").BuildOptions}
*/
@@ -158,7 +183,7 @@ export const commonOpts = {
sourcemap: watch ? "inline" : "",
legalComments: "linked",
banner,
- plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin],
+ plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
external: ["~plugins", "~git-hash", "~git-remote"],
inject: ["./scripts/build/inject/react.mjs"],
jsxFactory: "VencordCreateElement",
diff --git a/scripts/build/module/style.js b/scripts/build/module/style.js
new file mode 100644
index 0000000..5981a3d
--- /dev/null
+++ b/scripts/build/module/style.js
@@ -0,0 +1,26 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+(window.VencordStyles ??= new Map()).set(STYLE_NAME, {
+ name: STYLE_NAME,
+ source: STYLE_SOURCE,
+ classNames: {},
+ dom: null,
+});
+
+export default STYLE_NAME;
diff --git a/src/Vencord.ts b/src/Vencord.ts
index 464be2d..48e628f 100644
--- a/src/Vencord.ts
+++ b/src/Vencord.ts
@@ -18,7 +18,6 @@
export * as Api from "./api";
export * as Plugins from "./plugins";
-// eslint-disable-next-line @typescript-eslint/no-restricted-imports
export * as Util from "./utils";
export * as QuickCss from "./utils/quickCss";
export * as Updater from "./utils/updater";
diff --git a/src/api/Styles.ts b/src/api/Styles.ts
new file mode 100644
index 0000000..6b189ca
--- /dev/null
+++ b/src/api/Styles.ts
@@ -0,0 +1,162 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+import type { MapValue } from "type-fest/source/entry";
+
+export type Style = MapValue<typeof VencordStyles>;
+
+export const styleMap = window.VencordStyles ??= new Map();
+
+export function requireStyle(name: string) {
+ const style = styleMap.get(name);
+ if (!style) throw new Error(`Style "${name}" does not exist`);
+ return style;
+}
+
+/**
+ * A style's name can be obtained from importing a stylesheet with `?managed` at the end of the import
+ * @param name The name of the style
+ * @returns `false` if the style was already enabled, `true` otherwise
+ * @example
+ * import pluginStyle from "./plugin.css?managed";
+ *
+ * // Inside some plugin method like "start()" or "[option].onChange()"
+ * enableStyle(pluginStyle);
+ */
+export function enableStyle(name: string) {
+ const style = requireStyle(name);
+
+ if (style.dom?.isConnected)
+ return false;
+
+ if (!style.dom) {
+ style.dom = document.createElement("style");
+ style.dom.dataset.vencordName = style.name;
+ }
+ compileStyle(style);
+
+ document.head.appendChild(style.dom);
+ return true;
+}
+
+/**
+ * @param name The name of the style
+ * @returns `false` if the style was already disabled, `true` otherwise
+ * @see {@link enableStyle} for info on getting the name of an imported style
+ */
+export function disableStyle(name: string) {
+ const style = requireStyle(name);
+ if (!style.dom?.isConnected)
+ return false;
+
+ style.dom.remove();
+ style.dom = null;
+ return true;
+}
+
+/**
+ * @param name The name of the style
+ * @returns `true` in most cases, may return `false` in some edge cases
+ * @see {@link enableStyle} for info on getting the name of an imported style
+ */
+export const toggleStyle = (name: string) => isStyleEnabled(name) ? disableStyle(name) : enableStyle(name);
+
+/**
+ * @param name The name of the style
+ * @returns Whether the style is enabled
+ * @see {@link enableStyle} for info on getting the name of an imported style
+ */
+export const isStyleEnabled = (name: string) => requireStyle(name).dom?.isConnected ?? false;
+
+/**
+ * Sets the variables of a style
+ * ```ts
+ * // -- plugin.ts --
+ * import pluginStyle from "./plugin.css?managed";
+ * import { setStyleVars } from "@api/Styles";
+ * import { findByPropsLazy } from "@webpack";
+ * const classNames = findByPropsLazy("thin", "scrollerBase"); // { thin: "thin-31rlnD scrollerBase-_bVAAt", ... }
+ *
+ * // Inside some plugin method like "start()"
+ * setStyleClassNames(pluginStyle, classNames);
+ * enableStyle(pluginStyle);
+ * ```
+ * ```scss
+ * // -- plugin.css --
+ * .plugin-root [--thin]::-webkit-scrollbar { ... }
+ * ```
+ * ```scss
+ * // -- final stylesheet --
+ * .plugin-root .thin-31rlnD.scrollerBase-_bVAAt::-webkit-scrollbar { ... }
+ * ```
+ * @param name The name of the style
+ * @param classNames An object where the keys are the variable names and the values are the variable values
+ * @param recompile Whether to recompile the style after setting the variables, defaults to `true`
+ * @see {@link enableStyle} for info on getting the name of an imported style
+ */
+export const setStyleClassNames = (name: string, classNames: Record<string, string>, recompile = true) => {
+ const style = requireStyle(name);
+ style.classNames = classNames;
+ if (recompile && isStyleEnabled(style.name))
+ compileStyle(style);
+};
+
+/**
+ * Updates the stylesheet after doing the following to the sourcecode:
+ * - Interpolate style classnames
+ * @param style **_Must_ be a style with a DOM element**
+ * @see {@link setStyleClassNames} for more info on style classnames
+ */
+export const compileStyle = (style: Style) => {
+ if (!style.dom) throw new Error("Style has no DOM element");
+
+ style.dom.textContent = style.source
+ .replace(/\[--(\w+)\]/g, (match, name) => {
+ const className = style.classNames[name];
+ return className ? classNameToSelector(className) : match;
+ });
+};
+
+/**
+ * @param name The classname
+ * @param prefix A prefix to add each class, defaults to `""`
+ * @return A css selector for the classname
+ * @example
+ * classNameToSelector("foo bar") // => ".foo.bar"
+ */
+export const classNameToSelector = (name: string, prefix = "") => name.split(" ").map(n => `.${prefix}${n}`).join("");
+
+type ClassNameFactoryArg = string | string[] | Record<string, unknown>;
+/**
+ * @param prefix The prefix to add to each class, defaults to `""`
+ * @returns A classname generator function
+ * @example
+ * const cl = classNameFactory("plugin-");
+ *
+ * cl("base", ["item", "editable"], { selected: null, disabled: true })
+ * // => "plugin-base plugin-item plugin-editable plugin-disabled"
+ */
+export const classNameFactory = (prefix: string = "") => (...args: ClassNameFactoryArg[]) => {
+ const classNames = new Set<string>();
+ for (const arg of args) {
+ if (typeof arg === "string") classNames.add(arg);
+ else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name));
+ else if (typeof arg === "object") Object.entries(arg).forEach(([name, value]) => value && classNames.add(name));
+ }
+ return Array.from(classNames, name => prefix + name).join(" ");
+};
diff --git a/src/api/index.ts b/src/api/index.ts
index 7e981e2..0fef99c 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -26,6 +26,7 @@ import * as $MessageEventsAPI from "./MessageEvents";
import * as $MessagePopover from "./MessagePopover";
import * as $Notices from "./Notices";
import * as $ServerList from "./ServerList";
+import * as $Styles from "./Styles";
/**
* An API allowing you to listen to Message Clicks or run your own logic
@@ -33,16 +34,16 @@ import * as $ServerList from "./ServerList";
*
* If your plugin uses this, you must add MessageEventsAPI to its dependencies
*/
-const MessageEvents = $MessageEventsAPI;
+export const MessageEvents = $MessageEventsAPI;
/**
* An API allowing you to create custom notices
* (snackbars on the top, like the Update prompt)
*/
-const Notices = $Notices;
+export const Notices = $Notices;
/**
* An API allowing you to register custom commands
*/
-const Commands = $Commands;
+export const Commands = $Commands;
/**
* A wrapper around IndexedDB. This can store arbitrarily
* large data and supports a lot of datatypes (Blob, Map, ...).
@@ -57,30 +58,33 @@ const Commands = $Commands;
* This is actually just idb-keyval, so if you're familiar with that, you're golden!
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types}
*/
-const DataStore = $DataStore;
+export const DataStore = $DataStore;
/**
* An API allowing you to add custom components as message accessories
*/
-const MessageAccessories = $MessageAccessories;
+export const MessageAccessories = $MessageAccessories;
/**
* An API allowing you to add custom buttons in the message popover
*/
-const MessagePopover = $MessagePopover;
+export const MessagePopover = $MessagePopover;
/**
* An API allowing you to add badges to user profiles
*/
-const Badges = $Badges;
+export const Badges = $Badges;
/**
* An API allowing you to add custom elements to the server list
*/
-const ServerList = $ServerList;
+export const ServerList = $ServerList;
/**
* An API allowing you to add components as message accessories
*/
-const MessageDecorations = $MessageDecorations;
+export const MessageDecorations = $MessageDecorations;
/**
* An API allowing you to add components to member list users, in both DM's and servers
*/
-const MemberListDecorators = $MemberListDecorators;
-
-export { Badges, Commands, DataStore, MemberListDecorators, MessageAccessories, MessageDecorations, MessageEvents, MessagePopover, Notices, ServerList };
+export const MemberListDecorators = $MemberListDecorators;
+/**
+ * An API allowing you to dynamically load styles
+ * a
+ */
+export const Styles = $Styles;
diff --git a/src/components/VencordSettings/index.tsx b/src/components/VencordSettings/index.tsx
index b49e4b4..b3a3322 100644
--- a/src/components/VencordSettings/index.tsx
+++ b/src/components/VencordSettings/index.tsx
@@ -16,22 +16,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import "./settingsStyles.css";
+
import ErrorBoundary from "@components/ErrorBoundary";
import { findByCodeLazy } from "@webpack";
import { Forms, Router, Text } from "@webpack/common";
-import cssText from "~fileContent/settingsStyles.css";
-
import BackupRestoreTab from "./BackupRestoreTab";
import PluginsTab from "./PluginsTab";
import ThemesTab from "./ThemesTab";
import Updater from "./Updater";
import VencordSettings from "./VencordTab";
-const style = document.createElement("style");
-style.textContent = cssText;
-document.head.appendChild(style);
-
const st = (style: string) => `vcSettings${style}`;
const TabBar = findByCodeLazy('[role="tab"][aria-disabled="false"]');
diff --git a/src/globals.d.ts b/src/globals.d.ts
index 2e8d444..6c5b437 100644
--- a/src/globals.d.ts
+++ b/src/globals.d.ts
@@ -38,6 +38,12 @@ declare global {
export var VencordNative: typeof import("./VencordNative").default;
export var Vencord: typeof import("./Vencord");
+ export var VencordStyles: Map<string, {
+ name: string;
+ source: string;
+ classNames: Record<string, string>;
+ dom: HTMLStyleElement | null;
+ }>;
export var appSettings: {
set(setting: string, v: any): void;
};
diff --git a/src/ipcMain/index.ts b/src/ipcMain/index.ts
index 86a233c..ae8a96d 100644
--- a/src/ipcMain/index.ts
+++ b/src/ipcMain/index.ts
@@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import "./legacy";
import "./updater";
import { debounce } from "@utils/debounce";
diff --git a/src/ipcMain/legacy.ts b/src/ipcMain/legacy.ts
new file mode 100644
index 0000000..567ad3d
--- /dev/null
+++ b/src/ipcMain/legacy.ts
@@ -0,0 +1,31 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+import IpcEvents from "@utils/IpcEvents";
+import { ipcMain } from "electron";
+import { writeFile } from "fs/promises";
+import { join } from "path";
+
+import { get } from "./simpleGet";
+
+ipcMain.handleOnce(IpcEvents.DOWNLOAD_VENCORD_CSS, async () => {
+ const buf = await get("https://github.com/Vendicated/Vencord/releases/download/devbuild/renderer.css");
+ await writeFile(join(__dirname, "renderer.css"), buf);
+ return buf.toString("utf-8");
+});
+
diff --git a/src/ipcMain/updater/common.ts b/src/ipcMain/updater/common.ts
index 41f08e8..3729c6d 100644
--- a/src/ipcMain/updater/common.ts
+++ b/src/ipcMain/updater/common.ts
@@ -24,7 +24,7 @@ export async function calculateHashes() {
const hashes = {} as Record<string, string>;
await Promise.all(
- ["patcher.js", "preload.js", "renderer.js"].map(file => new Promise<void>(r => {
+ ["patcher.js", "preload.js", "renderer.js", "renderer.css"].map(file => new Promise<void>(r => {
const fis = createReadStream(join(__dirname, file));
const hash = createHash("sha1", { encoding: "hex" });
fis.once("end", () => {
diff --git a/src/ipcMain/updater/http.ts b/src/ipcMain/updater/http.ts
index 902d644..3b38144 100644
--- a/src/ipcMain/updater/http.ts
+++ b/src/ipcMain/updater/http.ts
@@ -69,7 +69,7 @@ async function fetchUpdates() {
return false;
data.assets.forEach(({ name, browser_download_url }) => {
- if (["patcher.js", "preload.js", "renderer.js"].some(s => name.startsWith(s))) {
+ if (["patcher.js", "preload.js", "renderer.js", "renderer.css"].some(s => name.startsWith(s))) {
PendingUpdates.push([name, browser_download_url]);
}
});
diff --git a/src/modules.d.ts b/src/modules.d.ts
index 6901260..c1a1996 100644
--- a/src/modules.d.ts
+++ b/src/modules.d.ts
@@ -37,3 +37,9 @@ declare module "~fileContent/*" {
const content: string;
export default content;
}
+
+declare module "*.css" { }
+declare module "*.css?managed" {
+ const name: string;
+ export default name;
+}
diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx
index abdb2f2..e650dbb 100644
--- a/src/plugins/messageLogger/index.tsx
+++ b/src/plugins/messageLogger/index.tsx
@@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import "./messageLogger.css";
+
import { Settings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
@@ -42,51 +44,14 @@ export default definePlugin({
timestampModule: null as any,
moment: null as Function | null,
- css: `
- .messagelogger-red-overlay .messageLogger-deleted {
- background-color: rgba(240, 71, 71, 0.15);
- }
- .messagelogger-red-text .messageLogger-deleted div {
- color: #f04747;
- }
-
- .messageLogger-deleted [class^="buttons"] {
- display: none;
- }
-
- .messageLogger-deleted-attachment {
- filter: grayscale(1);
- }
-
- .messageLogger-deleted-attachment:hover {
- filter: grayscale(0);
- transition: 250ms filter linear;
- }
-
- .theme-dark .messageLogger-edited {
- filter: brightness(80%);
- }
-
- .theme-light .messageLogger-edited {
- opacity: 0.5;
- }
- `,
-
start() {
this.moment = findByPropsLazy("relativeTimeRounding", "relativeTimeThreshold");
this.timestampModule = findByPropsLazy("messageLogger_TimestampComponent");
- const style = this.style = document.createElement("style");
- style.textContent = this.css;
- style.id = "MessageLogger-css";
- document.head.appendChild(style);
-
addDeleteStyleClass();
},
stop() {
- this.style?.remove();
-
document.querySelectorAll(".messageLogger-deleted").forEach(e => e.remove());
document.querySelectorAll(".messageLogger-edited").forEach(e => e.remove());
document.body.classList.remove("messagelogger-red-overlay");
diff --git a/src/plugins/messageLogger/messageLogger.css b/src/plugins/messageLogger/messageLogger.css
new file mode 100644
index 0000000..94a3e25
--- /dev/null
+++ b/src/plugins/messageLogger/messageLogger.css
@@ -0,0 +1,27 @@
+.messagelogger-red-overlay .messageLogger-deleted {
+ background-color: rgba(240, 71, 71, 0.15);
+}
+.messagelogger-red-text .messageLogger-deleted div {
+ color: #f04747;
+}
+
+.messageLogger-deleted [class^="buttons"] {
+ display: none;
+}
+
+.messageLogger-deleted-attachment {
+ filter: grayscale(1);
+}
+
+.messageLogger-deleted-attachment:hover {
+ filter: grayscale(0);
+ transition: 250ms filter linear;
+}
+
+.theme-dark .messageLogger-edited {
+ filter: brightness(80%);
+}
+
+.theme-light .messageLogger-edited {
+ opacity: 0.5;
+}
diff --git a/src/plugins/shikiCodeblocks/components/Header.tsx b/src/plugins/shikiCodeblocks/components/Header.tsx
index c2db386..320dde9 100644
--- a/src/plugins/shikiCodeblocks/components/Header.tsx
+++ b/src/plugins/shikiCodeblocks/components/Header.tsx
@@ -33,7 +33,7 @@ export function Header({ langName, useDevIcon, shikiLang }: HeaderProps) {
<div className={cl("lang")}>
{useDevIcon !== DeviconSetting.Disabled && shikiLang?.devicon && (
<i
- className={`devicon-${shikiLang.devicon}${useDevIcon === DeviconSetting.Color ? " colored" : ""}`}
+ className={`${cl("devicon")} devicon-${shikiLang.devicon}${useDevIcon === DeviconSetting.Color ? " colored" : ""}`}
/>
)}
{langName}
diff --git a/src/plugins/shikiCodeblocks/components/Highlighter.tsx b/src/plugins/shikiCodeblocks/components/Highlighter.tsx
index d26cd81..badb3c8 100644
--- a/src/plugins/shikiCodeblocks/components/Highlighter.tsx
+++ b/src/plugins/shikiCodeblocks/components/Highlighter.tsx
@@ -90,14 +90,10 @@ export const Highlighter = ({
let langName;
if (lang) langName = useHljs ? hljs?.getLanguage?.(lang)?.name : shikiLang?.name;
- const preClasses = [cl("root")];
- if (!langName) preClasses.push(cl("plain"));
- if (isPreview) preClasses.push(cl("preview"));
-
return (
<div
ref={rootRef}
- className={preClasses.join(" ")}
+ className={cl("root", { plain: !langName, preview: isPreview })}
style={{
backgroundColor: useHljs
? themeBase.backgroundColor
diff --git a/src/plugins/shikiCodeblocks/devicon.css b/src/plugins/shikiCodeblocks/devicon.css
new file mode 100644
index 0000000..f5c4921
--- /dev/null
+++ b/src/plugins/shikiCodeblocks/devicon.css
@@ -0,0 +1 @@
+@import url('https://cdn.jsdelivr.net/gh/devicons/devicon@v2.10.1/devicon.min.css');
diff --git a/src/plugins/shikiCodeblocks/index.ts b/src/plugins/shikiCodeblocks/index.ts
index 58e0048..428a273 100644
--- a/src/plugins/shikiCodeblocks/index.ts
+++ b/src/plugins/shikiCodeblocks/index.ts
@@ -16,23 +16,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import "./shiki.css";
+
+import { disableStyle, enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants";
import { parseUrl } from "@utils/misc";
import { wordsFromPascal, wordsToTitle } from "@utils/text";
import definePlugin, { OptionType } from "@utils/types";
import previewExampleText from "~fileContent/previewExample.tsx";
-import cssText from "~fileContent/shiki.css";
import { Settings } from "../../Vencord";
import { shiki } from "./api/shiki";
import { themes } from "./api/themes";
import { createHighlighter } from "./components/Highlighter";
-import { DeviconSetting, HljsSetting, ShikiSettings, StyleSheets } from "./types";
-import { clearStyles, removeStyle, setStyle } from "./utils/createStyle";
+import deviconStyle from "./devicon.css?managed";
+import { DeviconSetting, HljsSetting, ShikiSettings } from "./types";
+import { clearStyles } from "./utils/createStyle";
const themeNames = Object.keys(themes);
-const devIconCss = "@import url('https://cdn.jsdelivr.net/gh/devicons/devicon@v2.10.1/devicon.min.css');";
const getSettings = () => Settings.plugins.ShikiCodeblocks as ShikiSettings;
@@ -50,9 +52,8 @@ export default definePlugin({
},
],
start: async () => {
- setStyle(cssText, StyleSheets.Main);
if (getSettings().useDevIcon !== DeviconSetting.Disabled)
- setStyle(devIconCss, StyleSheets.DevIcons);
+ enableStyle(deviconStyle);
await shiki.init(getSettings().customTheme || getSettings().theme);
},
@@ -135,8 +136,8 @@ export default definePlugin({
},
],
onChange: (newValue: DeviconSetting) => {
- if (newValue === DeviconSetting.Disabled) removeStyle(StyleSheets.DevIcons);
- else setStyle(devIconCss, StyleSheets.DevIcons);
+ if (newValue === DeviconSetting.Disabled) disableStyle(deviconStyle);
+ else enableStyle(deviconStyle);
},
},
bgOpacity: {
diff --git a/src/plugins/shikiCodeblocks/shiki.css b/src/plugins/shikiCodeblocks/shiki.css
index b871d99..d71b673 100644
--- a/src/plugins/shikiCodeblocks/shiki.css
+++ b/src/plugins/shikiCodeblocks/shiki.css
@@ -1,6 +1,5 @@
.shiki-container {
border: 4px;
- /* fallback background */
background-color: var(--background-secondary);
}
@@ -22,8 +21,7 @@
border: none;
}
-.shiki-root [class^='devicon-'],
-.shiki-root [class*=' devicon-'] {
+.shiki-devicon {
margin-right: 8px;
user-select: none;
}
diff --git a/src/plugins/shikiCodeblocks/utils/misc.ts b/src/plugins/shikiCodeblocks/utils/misc.ts
index 1342ff5..fefe938 100644
--- a/src/plugins/shikiCodeblocks/utils/misc.ts
+++ b/src/plugins/shikiCodeblocks/utils/misc.ts
@@ -16,13 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import { classNameFactory } from "@api/Styles";
import { hljs } from "@webpack/common";
import { resolveLang } from "../api/languages";
import { HighlighterProps } from "../components/Highlighter";
import { HljsSetting, ShikiSettings } from "../types";
-export const cl = (className: string) => `shiki-${className}`;
+export const cl = classNameFactory("shiki-");
export const shouldUseHljs = ({
lang,
diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx
index af53f59..f6ad08b 100644
--- a/src/plugins/spotifyControls/PlayerComponent.tsx
+++ b/src/plugins/spotifyControls/PlayerComponent.tsx
@@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import "./spotifyStyles.css";
+
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Link } from "@components/Link";
diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts
index 75448dc..641ba1a 100644
--- a/src/plugins/spotifyControls/SpotifyStore.ts
+++ b/src/plugins/spotifyControls/SpotifyStore.ts
@@ -21,8 +21,6 @@ import { proxyLazy } from "@utils/proxyLazy";
import { findByPropsLazy } from "@webpack";
import { Flux, FluxDispatcher } from "@webpack/common";
-import cssText from "~fileContent/spotifyStyles.css";
-
export interface Track {
id: string;
name: string;
@@ -69,11 +67,6 @@ type Repeat = "off" | "track" | "context";
// Don't wanna run before Flux and Dispatcher are ready!
export const SpotifyStore = proxyLazy(() => {
- // TODO: Move this elsewhere
- const style = document.createElement("style");
- style.innerText = cssText;
- document.head.appendChild(style);
-
// For some reason ts hates extends Flux.Store
const { Store } = Flux;
diff --git a/src/preload.ts b/src/preload.ts
index dcf2554..7460081 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -44,6 +44,34 @@ contextBridge.exposeInMainWorld("VencordNative", VencordNative);
if (location.protocol !== "data:") {
// Discord
webFrame.executeJavaScript(readFileSync(join(__dirname, "renderer.js"), "utf-8"));
+ const rendererCss = join(__dirname, "renderer.css");
+
+ function insertCss(css: string) {
+ const style = document.createElement("style");
+ style.id = "vencord-css-core";
+ style.textContent = css;
+
+ if (document.readyState === "complete") {
+ document.documentElement.appendChild(style);
+ } else {
+ document.addEventListener("DOMContentLoaded", () => document.documentElement.appendChild(style), {
+ once: true
+ });
+ }
+ }
+
+ try {
+ const css = readFileSync(rendererCss, "utf-8");
+ insertCss(css);
+ } catch (err) {
+ if ((err as NodeJS.ErrnoException)?.code !== "ENOENT")
+ throw err;
+
+ // hack: the pre update updater does not download this file, so manually download it
+ // TODO: remove this in a future version
+ ipcRenderer.invoke(IpcEvents.DOWNLOAD_VENCORD_CSS)
+ .then(insertCss);
+ }
require(process.env.DISCORD_PRELOAD!);
} else {
// Monaco Popout
diff --git a/src/utils/IpcEvents.ts b/src/utils/IpcEvents.ts
index c6696f8..345146b 100644
--- a/src/utils/IpcEvents.ts
+++ b/src/utils/IpcEvents.ts
@@ -44,5 +44,6 @@ export default strEnum({
UPDATE: "VencordUpdate",
BUILD: "VencordBuild",
GET_DESKTOP_CAPTURE_SOURCES: "VencordGetDesktopCaptureSources",
- OPEN_MONACO_EDITOR: "VencordOpenMonacoEditor"
+ OPEN_MONACO_EDITOR: "VencordOpenMonacoEditor",
+ DOWNLOAD_VENCORD_CSS: "VencordDownloadVencordCss"
} as const);
diff --git a/src/utils/updater.ts b/src/utils/updater.ts
index 2ea4953..04205a5 100644
--- a/src/utils/updater.ts
+++ b/src/utils/updater.ts
@@ -61,7 +61,7 @@ export function getRepo() {
return Unwrap(VencordNative.ipc.invoke<IpcRes<string>>(IpcEvents.GET_REPO));
}
-type Hashes = Record<"patcher.js" | "preload.js" | "renderer.js", string>;
+type Hashes = Record<"patcher.js" | "preload.js" | "renderer.js" | "renderer.css", string>;
/**
* @returns true if hard restart is required
diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx
index f2c42d1..a732d6b 100644
--- a/src/webpack/common.tsx
+++ b/src/webpack/common.tsx
@@ -36,6 +36,7 @@ export let React: typeof import("react");
export let useState: typeof React.useState;
export let useEffect: typeof React.useEffect;
export let useMemo: typeof React.useMemo;
+export let useRef: typeof React.useRef;
export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPortal", "render");
@@ -158,7 +159,7 @@ export const NavigationRouter = mapMangledModuleLazy("Transitioning to external
waitFor("useState", m => {
React = m;
- ({ useEffect, useState, useMemo } = React);
+ ({ useEffect, useState, useMemo, useRef } = React);
});
waitFor(["dispatch", "subscribe"], m => {