feat: Proper CSS api & css bundle (#269)
Co-authored-by: Vap0r1ze <superdash993@gmail.com>
This commit is contained in:
parent
2172cae779
commit
2e5d27b6b6
31 changed files with 438 additions and 126 deletions
|
@ -2,7 +2,18 @@ if (typeof browser === "undefined") {
|
||||||
var browser = chrome;
|
var browser = chrome;
|
||||||
}
|
}
|
||||||
|
|
||||||
var script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.src = browser.runtime.getURL("dist/Vencord.js");
|
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 }
|
||||||
|
);
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"js": ["content.js"]
|
"js": ["content.js"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"web_accessible_resources": ["dist/Vencord.js"],
|
"web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"],
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["background.js"]
|
"scripts": ["background.js"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
{
|
{
|
||||||
"resources": ["dist/Vencord.js"],
|
"resources": ["dist/Vencord.js", "dist/Vencord.css"],
|
||||||
"matches": ["*://*.discord.com/*"]
|
"matches": ["*://*.discord.com/*"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
11
package.json
11
package.json
|
@ -65,6 +65,17 @@
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
|
"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"
|
"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": {
|
"webExt": {
|
||||||
|
|
66
scripts/build/buildWeb.mjs
Executable file → Normal file
66
scripts/build/buildWeb.mjs
Executable file → Normal file
|
@ -20,13 +20,13 @@
|
||||||
|
|
||||||
import esbuild from "esbuild";
|
import esbuild from "esbuild";
|
||||||
import { zip } from "fflate";
|
import { zip } from "fflate";
|
||||||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { readFile } from "fs/promises";
|
import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
|
||||||
import { join, resolve } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
// wtf is this assert syntax
|
// wtf is this assert syntax
|
||||||
import PackageJSON from "../../package.json" assert { type: "json" };
|
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}
|
* @type {esbuild.BuildOptions}
|
||||||
|
@ -39,9 +39,7 @@ const commonOptions = {
|
||||||
external: ["plugins", "git-hash"],
|
external: ["plugins", "git-hash"],
|
||||||
plugins: [
|
plugins: [
|
||||||
globPlugins,
|
globPlugins,
|
||||||
gitHashPlugin,
|
...commonOpts.plugins,
|
||||||
gitRemotePlugin,
|
|
||||||
fileIncludePlugin
|
|
||||||
],
|
],
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
define: {
|
define: {
|
||||||
|
@ -77,9 +75,13 @@ await Promise.all(
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {(target: string, files: string[], shouldZip: boolean) => Promise<void>}
|
||||||
|
*/
|
||||||
async function buildPluginZip(target, files, shouldZip) {
|
async function buildPluginZip(target, files, shouldZip) {
|
||||||
const entries = {
|
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 => [
|
...Object.fromEntries(await Promise.all(files.map(async f => [
|
||||||
(f.startsWith("manifest") ? "manifest.json" : f),
|
(f.startsWith("manifest") ? "manifest.json" : f),
|
||||||
await readFile(join("browser", f))
|
await readFile(join("browser", f))
|
||||||
|
@ -87,29 +89,47 @@ async function buildPluginZip(target, files, shouldZip) {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (shouldZip) {
|
if (shouldZip) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
zip(entries, {}, (err, data) => {
|
zip(entries, {}, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
reject(err);
|
||||||
process.exitCode = 1;
|
|
||||||
} else {
|
} else {
|
||||||
writeFileSync("dist/" + target, data);
|
const out = join("dist", target);
|
||||||
console.info("Extension written to dist/" + target);
|
writeFile(out, data).then(() => {
|
||||||
|
console.info("Extension written to " + out);
|
||||||
|
resolve();
|
||||||
|
}).catch(reject);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
if (existsSync(target))
|
await rm(target, { recursive: true, force: true });
|
||||||
rmSync(target, { recursive: true });
|
await Promise.all(Object.entries(entries).map(async ([file, content]) => {
|
||||||
for (const entry in entries) {
|
const dest = join("dist", target, file);
|
||||||
const destination = "dist/" + target + "/" + entry;
|
const parentDirectory = join(dest, "..");
|
||||||
const parentDirectory = resolve(destination, "..");
|
await mkdir(parentDirectory, { recursive: true });
|
||||||
mkdirSync(parentDirectory, { recursive: true });
|
await writeFile(dest, content);
|
||||||
writeFileSync(destination, entries[entry]);
|
}));
|
||||||
}
|
|
||||||
console.info("Unpacked Extension written to dist/" + target);
|
console.info("Unpacked Extension written to dist/" + target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await buildPluginZip("extension-v3.zip", ["modifyResponseHeaders.json", "content.js", "manifestv3.json"], true);
|
const cssText = "`" + readFileSync("dist/Vencord.user.css", "utf-8").replaceAll("`", "\\`") + "`";
|
||||||
await buildPluginZip("extension-v2.zip", ["background.js", "content.js", "manifestv2.json"], true);
|
const cssRuntime = `
|
||||||
await buildPluginZip("extension-v2-unpacked", ["background.js", "content.js", "manifestv2.json"], false);
|
;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),
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { exec, execSync } from "child_process";
|
import { exec, execSync } from "child_process";
|
||||||
import { existsSync } from "fs";
|
import { existsSync, readFileSync } from "fs";
|
||||||
import { readdir, readFile } from "fs/promises";
|
import { readdir, readFile } from "fs/promises";
|
||||||
import { join } from "path";
|
import { join, relative } from "path";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
|
|
||||||
export const watch = process.argv.includes("--watch");
|
export const watch = process.argv.includes("--watch");
|
||||||
|
@ -35,7 +35,7 @@ export const banner = {
|
||||||
|
|
||||||
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
|
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
|
||||||
/**
|
/**
|
||||||
* @type {esbuild.Plugin}
|
* @type {import("esbuild").Plugin}
|
||||||
*/
|
*/
|
||||||
export const makeAllPackagesExternalPlugin = {
|
export const makeAllPackagesExternalPlugin = {
|
||||||
name: "make-all-packages-external",
|
name: "make-all-packages-external",
|
||||||
|
@ -46,7 +46,7 @@ export const makeAllPackagesExternalPlugin = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {esbuild.Plugin}
|
* @type {import("esbuild").Plugin}
|
||||||
*/
|
*/
|
||||||
export const globPlugins = {
|
export const globPlugins = {
|
||||||
name: "glob-plugins",
|
name: "glob-plugins",
|
||||||
|
@ -87,7 +87,7 @@ export const globPlugins = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {esbuild.Plugin}
|
* @type {import("esbuild").Plugin}
|
||||||
*/
|
*/
|
||||||
export const gitHashPlugin = {
|
export const gitHashPlugin = {
|
||||||
name: "git-hash-plugin",
|
name: "git-hash-plugin",
|
||||||
|
@ -103,7 +103,7 @@ export const gitHashPlugin = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {esbuild.Plugin}
|
* @type {import("esbuild").Plugin}
|
||||||
*/
|
*/
|
||||||
export const gitRemotePlugin = {
|
export const gitRemotePlugin = {
|
||||||
name: "git-remote-plugin",
|
name: "git-remote-plugin",
|
||||||
|
@ -125,7 +125,7 @@ export const gitRemotePlugin = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {esbuild.Plugin}
|
* @type {import("esbuild").Plugin}
|
||||||
*/
|
*/
|
||||||
export const fileIncludePlugin = {
|
export const fileIncludePlugin = {
|
||||||
name: "file-include-plugin",
|
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}
|
* @type {import("esbuild").BuildOptions}
|
||||||
*/
|
*/
|
||||||
|
@ -158,7 +183,7 @@ export const commonOpts = {
|
||||||
sourcemap: watch ? "inline" : "",
|
sourcemap: watch ? "inline" : "",
|
||||||
legalComments: "linked",
|
legalComments: "linked",
|
||||||
banner,
|
banner,
|
||||||
plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin],
|
plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
|
||||||
external: ["~plugins", "~git-hash", "~git-remote"],
|
external: ["~plugins", "~git-hash", "~git-remote"],
|
||||||
inject: ["./scripts/build/inject/react.mjs"],
|
inject: ["./scripts/build/inject/react.mjs"],
|
||||||
jsxFactory: "VencordCreateElement",
|
jsxFactory: "VencordCreateElement",
|
||||||
|
|
26
scripts/build/module/style.js
Normal file
26
scripts/build/module/style.js
Normal file
|
@ -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;
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
export * as Api from "./api";
|
export * as Api from "./api";
|
||||||
export * as Plugins from "./plugins";
|
export * as Plugins from "./plugins";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
|
||||||
export * as Util from "./utils";
|
export * as Util from "./utils";
|
||||||
export * as QuickCss from "./utils/quickCss";
|
export * as QuickCss from "./utils/quickCss";
|
||||||
export * as Updater from "./utils/updater";
|
export * as Updater from "./utils/updater";
|
||||||
|
|
162
src/api/Styles.ts
Normal file
162
src/api/Styles.ts
Normal file
|
@ -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(" ");
|
||||||
|
};
|
|
@ -26,6 +26,7 @@ import * as $MessageEventsAPI from "./MessageEvents";
|
||||||
import * as $MessagePopover from "./MessagePopover";
|
import * as $MessagePopover from "./MessagePopover";
|
||||||
import * as $Notices from "./Notices";
|
import * as $Notices from "./Notices";
|
||||||
import * as $ServerList from "./ServerList";
|
import * as $ServerList from "./ServerList";
|
||||||
|
import * as $Styles from "./Styles";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An API allowing you to listen to Message Clicks or run your own logic
|
* 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
|
* 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
|
* An API allowing you to create custom notices
|
||||||
* (snackbars on the top, like the Update prompt)
|
* (snackbars on the top, like the Update prompt)
|
||||||
*/
|
*/
|
||||||
const Notices = $Notices;
|
export const Notices = $Notices;
|
||||||
/**
|
/**
|
||||||
* An API allowing you to register custom commands
|
* An API allowing you to register custom commands
|
||||||
*/
|
*/
|
||||||
const Commands = $Commands;
|
export const Commands = $Commands;
|
||||||
/**
|
/**
|
||||||
* A wrapper around IndexedDB. This can store arbitrarily
|
* A wrapper around IndexedDB. This can store arbitrarily
|
||||||
* large data and supports a lot of datatypes (Blob, Map, ...).
|
* 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!
|
* 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}
|
* @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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* An API allowing you to add components to member list users, in both DM's and servers
|
||||||
*/
|
*/
|
||||||
const MemberListDecorators = $MemberListDecorators;
|
export const MemberListDecorators = $MemberListDecorators;
|
||||||
|
/**
|
||||||
export { Badges, Commands, DataStore, MemberListDecorators, MessageAccessories, MessageDecorations, MessageEvents, MessagePopover, Notices, ServerList };
|
* An API allowing you to dynamically load styles
|
||||||
|
* a
|
||||||
|
*/
|
||||||
|
export const Styles = $Styles;
|
||||||
|
|
|
@ -16,22 +16,18 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./settingsStyles.css";
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { findByCodeLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { Forms, Router, Text } from "@webpack/common";
|
import { Forms, Router, Text } from "@webpack/common";
|
||||||
|
|
||||||
import cssText from "~fileContent/settingsStyles.css";
|
|
||||||
|
|
||||||
import BackupRestoreTab from "./BackupRestoreTab";
|
import BackupRestoreTab from "./BackupRestoreTab";
|
||||||
import PluginsTab from "./PluginsTab";
|
import PluginsTab from "./PluginsTab";
|
||||||
import ThemesTab from "./ThemesTab";
|
import ThemesTab from "./ThemesTab";
|
||||||
import Updater from "./Updater";
|
import Updater from "./Updater";
|
||||||
import VencordSettings from "./VencordTab";
|
import VencordSettings from "./VencordTab";
|
||||||
|
|
||||||
const style = document.createElement("style");
|
|
||||||
style.textContent = cssText;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
|
|
||||||
const st = (style: string) => `vcSettings${style}`;
|
const st = (style: string) => `vcSettings${style}`;
|
||||||
|
|
||||||
const TabBar = findByCodeLazy('[role="tab"][aria-disabled="false"]');
|
const TabBar = findByCodeLazy('[role="tab"][aria-disabled="false"]');
|
||||||
|
|
6
src/globals.d.ts
vendored
6
src/globals.d.ts
vendored
|
@ -38,6 +38,12 @@ declare global {
|
||||||
|
|
||||||
export var VencordNative: typeof import("./VencordNative").default;
|
export var VencordNative: typeof import("./VencordNative").default;
|
||||||
export var Vencord: typeof import("./Vencord");
|
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: {
|
export var appSettings: {
|
||||||
set(setting: string, v: any): void;
|
set(setting: string, v: any): void;
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./legacy";
|
||||||
import "./updater";
|
import "./updater";
|
||||||
|
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
|
|
31
src/ipcMain/legacy.ts
Normal file
31
src/ipcMain/legacy.ts
Normal file
|
@ -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");
|
||||||
|
});
|
||||||
|
|
|
@ -24,7 +24,7 @@ export async function calculateHashes() {
|
||||||
const hashes = {} as Record<string, string>;
|
const hashes = {} as Record<string, string>;
|
||||||
|
|
||||||
await Promise.all(
|
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 fis = createReadStream(join(__dirname, file));
|
||||||
const hash = createHash("sha1", { encoding: "hex" });
|
const hash = createHash("sha1", { encoding: "hex" });
|
||||||
fis.once("end", () => {
|
fis.once("end", () => {
|
||||||
|
|
|
@ -69,7 +69,7 @@ async function fetchUpdates() {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
data.assets.forEach(({ name, browser_download_url }) => {
|
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]);
|
PendingUpdates.push([name, browser_download_url]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
6
src/modules.d.ts
vendored
6
src/modules.d.ts
vendored
|
@ -37,3 +37,9 @@ declare module "~fileContent/*" {
|
||||||
const content: string;
|
const content: string;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "*.css" { }
|
||||||
|
declare module "*.css?managed" {
|
||||||
|
const name: string;
|
||||||
|
export default name;
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./messageLogger.css";
|
||||||
|
|
||||||
import { Settings } from "@api/settings";
|
import { Settings } from "@api/settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
@ -42,51 +44,14 @@ export default definePlugin({
|
||||||
timestampModule: null as any,
|
timestampModule: null as any,
|
||||||
moment: null as Function | null,
|
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() {
|
start() {
|
||||||
this.moment = findByPropsLazy("relativeTimeRounding", "relativeTimeThreshold");
|
this.moment = findByPropsLazy("relativeTimeRounding", "relativeTimeThreshold");
|
||||||
this.timestampModule = findByPropsLazy("messageLogger_TimestampComponent");
|
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();
|
addDeleteStyleClass();
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.style?.remove();
|
|
||||||
|
|
||||||
document.querySelectorAll(".messageLogger-deleted").forEach(e => e.remove());
|
document.querySelectorAll(".messageLogger-deleted").forEach(e => e.remove());
|
||||||
document.querySelectorAll(".messageLogger-edited").forEach(e => e.remove());
|
document.querySelectorAll(".messageLogger-edited").forEach(e => e.remove());
|
||||||
document.body.classList.remove("messagelogger-red-overlay");
|
document.body.classList.remove("messagelogger-red-overlay");
|
||||||
|
|
27
src/plugins/messageLogger/messageLogger.css
Normal file
27
src/plugins/messageLogger/messageLogger.css
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ export function Header({ langName, useDevIcon, shikiLang }: HeaderProps) {
|
||||||
<div className={cl("lang")}>
|
<div className={cl("lang")}>
|
||||||
{useDevIcon !== DeviconSetting.Disabled && shikiLang?.devicon && (
|
{useDevIcon !== DeviconSetting.Disabled && shikiLang?.devicon && (
|
||||||
<i
|
<i
|
||||||
className={`devicon-${shikiLang.devicon}${useDevIcon === DeviconSetting.Color ? " colored" : ""}`}
|
className={`${cl("devicon")} devicon-${shikiLang.devicon}${useDevIcon === DeviconSetting.Color ? " colored" : ""}`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{langName}
|
{langName}
|
||||||
|
|
|
@ -90,14 +90,10 @@ export const Highlighter = ({
|
||||||
let langName;
|
let langName;
|
||||||
if (lang) langName = useHljs ? hljs?.getLanguage?.(lang)?.name : shikiLang?.name;
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
className={preClasses.join(" ")}
|
className={cl("root", { plain: !langName, preview: isPreview })}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: useHljs
|
backgroundColor: useHljs
|
||||||
? themeBase.backgroundColor
|
? themeBase.backgroundColor
|
||||||
|
|
1
src/plugins/shikiCodeblocks/devicon.css
Normal file
1
src/plugins/shikiCodeblocks/devicon.css
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@import url('https://cdn.jsdelivr.net/gh/devicons/devicon@v2.10.1/devicon.min.css');
|
|
@ -16,23 +16,25 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 { Devs } from "@utils/constants";
|
||||||
import { parseUrl } from "@utils/misc";
|
import { parseUrl } from "@utils/misc";
|
||||||
import { wordsFromPascal, wordsToTitle } from "@utils/text";
|
import { wordsFromPascal, wordsToTitle } from "@utils/text";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
import previewExampleText from "~fileContent/previewExample.tsx";
|
import previewExampleText from "~fileContent/previewExample.tsx";
|
||||||
import cssText from "~fileContent/shiki.css";
|
|
||||||
|
|
||||||
import { Settings } from "../../Vencord";
|
import { Settings } from "../../Vencord";
|
||||||
import { shiki } from "./api/shiki";
|
import { shiki } from "./api/shiki";
|
||||||
import { themes } from "./api/themes";
|
import { themes } from "./api/themes";
|
||||||
import { createHighlighter } from "./components/Highlighter";
|
import { createHighlighter } from "./components/Highlighter";
|
||||||
import { DeviconSetting, HljsSetting, ShikiSettings, StyleSheets } from "./types";
|
import deviconStyle from "./devicon.css?managed";
|
||||||
import { clearStyles, removeStyle, setStyle } from "./utils/createStyle";
|
import { DeviconSetting, HljsSetting, ShikiSettings } from "./types";
|
||||||
|
import { clearStyles } from "./utils/createStyle";
|
||||||
|
|
||||||
const themeNames = Object.keys(themes);
|
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;
|
const getSettings = () => Settings.plugins.ShikiCodeblocks as ShikiSettings;
|
||||||
|
|
||||||
|
@ -50,9 +52,8 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
start: async () => {
|
start: async () => {
|
||||||
setStyle(cssText, StyleSheets.Main);
|
|
||||||
if (getSettings().useDevIcon !== DeviconSetting.Disabled)
|
if (getSettings().useDevIcon !== DeviconSetting.Disabled)
|
||||||
setStyle(devIconCss, StyleSheets.DevIcons);
|
enableStyle(deviconStyle);
|
||||||
|
|
||||||
await shiki.init(getSettings().customTheme || getSettings().theme);
|
await shiki.init(getSettings().customTheme || getSettings().theme);
|
||||||
},
|
},
|
||||||
|
@ -135,8 +136,8 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onChange: (newValue: DeviconSetting) => {
|
onChange: (newValue: DeviconSetting) => {
|
||||||
if (newValue === DeviconSetting.Disabled) removeStyle(StyleSheets.DevIcons);
|
if (newValue === DeviconSetting.Disabled) disableStyle(deviconStyle);
|
||||||
else setStyle(devIconCss, StyleSheets.DevIcons);
|
else enableStyle(deviconStyle);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
bgOpacity: {
|
bgOpacity: {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
.shiki-container {
|
.shiki-container {
|
||||||
border: 4px;
|
border: 4px;
|
||||||
/* fallback background */
|
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,8 +21,7 @@
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shiki-root [class^='devicon-'],
|
.shiki-devicon {
|
||||||
.shiki-root [class*=' devicon-'] {
|
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { hljs } from "@webpack/common";
|
import { hljs } from "@webpack/common";
|
||||||
|
|
||||||
import { resolveLang } from "../api/languages";
|
import { resolveLang } from "../api/languages";
|
||||||
import { HighlighterProps } from "../components/Highlighter";
|
import { HighlighterProps } from "../components/Highlighter";
|
||||||
import { HljsSetting, ShikiSettings } from "../types";
|
import { HljsSetting, ShikiSettings } from "../types";
|
||||||
|
|
||||||
export const cl = (className: string) => `shiki-${className}`;
|
export const cl = classNameFactory("shiki-");
|
||||||
|
|
||||||
export const shouldUseHljs = ({
|
export const shouldUseHljs = ({
|
||||||
lang,
|
lang,
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./spotifyStyles.css";
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
|
|
|
@ -21,8 +21,6 @@ import { proxyLazy } from "@utils/proxyLazy";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Flux, FluxDispatcher } from "@webpack/common";
|
import { Flux, FluxDispatcher } from "@webpack/common";
|
||||||
|
|
||||||
import cssText from "~fileContent/spotifyStyles.css";
|
|
||||||
|
|
||||||
export interface Track {
|
export interface Track {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -69,11 +67,6 @@ type Repeat = "off" | "track" | "context";
|
||||||
|
|
||||||
// Don't wanna run before Flux and Dispatcher are ready!
|
// Don't wanna run before Flux and Dispatcher are ready!
|
||||||
export const SpotifyStore = proxyLazy(() => {
|
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
|
// For some reason ts hates extends Flux.Store
|
||||||
const { Store } = Flux;
|
const { Store } = Flux;
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,34 @@ contextBridge.exposeInMainWorld("VencordNative", VencordNative);
|
||||||
if (location.protocol !== "data:") {
|
if (location.protocol !== "data:") {
|
||||||
// Discord
|
// Discord
|
||||||
webFrame.executeJavaScript(readFileSync(join(__dirname, "renderer.js"), "utf-8"));
|
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!);
|
require(process.env.DISCORD_PRELOAD!);
|
||||||
} else {
|
} else {
|
||||||
// Monaco Popout
|
// Monaco Popout
|
||||||
|
|
|
@ -44,5 +44,6 @@ export default strEnum({
|
||||||
UPDATE: "VencordUpdate",
|
UPDATE: "VencordUpdate",
|
||||||
BUILD: "VencordBuild",
|
BUILD: "VencordBuild",
|
||||||
GET_DESKTOP_CAPTURE_SOURCES: "VencordGetDesktopCaptureSources",
|
GET_DESKTOP_CAPTURE_SOURCES: "VencordGetDesktopCaptureSources",
|
||||||
OPEN_MONACO_EDITOR: "VencordOpenMonacoEditor"
|
OPEN_MONACO_EDITOR: "VencordOpenMonacoEditor",
|
||||||
|
DOWNLOAD_VENCORD_CSS: "VencordDownloadVencordCss"
|
||||||
} as const);
|
} as const);
|
||||||
|
|
|
@ -61,7 +61,7 @@ export function getRepo() {
|
||||||
return Unwrap(VencordNative.ipc.invoke<IpcRes<string>>(IpcEvents.GET_REPO));
|
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
|
* @returns true if hard restart is required
|
||||||
|
|
|
@ -36,6 +36,7 @@ export let React: typeof import("react");
|
||||||
export let useState: typeof React.useState;
|
export let useState: typeof React.useState;
|
||||||
export let useEffect: typeof React.useEffect;
|
export let useEffect: typeof React.useEffect;
|
||||||
export let useMemo: typeof React.useMemo;
|
export let useMemo: typeof React.useMemo;
|
||||||
|
export let useRef: typeof React.useRef;
|
||||||
|
|
||||||
export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPortal", "render");
|
export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPortal", "render");
|
||||||
|
|
||||||
|
@ -158,7 +159,7 @@ export const NavigationRouter = mapMangledModuleLazy("Transitioning to external
|
||||||
|
|
||||||
waitFor("useState", m => {
|
waitFor("useState", m => {
|
||||||
React = m;
|
React = m;
|
||||||
({ useEffect, useState, useMemo } = React);
|
({ useEffect, useState, useMemo, useRef } = React);
|
||||||
});
|
});
|
||||||
|
|
||||||
waitFor(["dispatch", "subscribe"], m => {
|
waitFor(["dispatch", "subscribe"], m => {
|
||||||
|
|
Loading…
Reference in a new issue