Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
thororen1234 2025-12-19 21:24:32 -05:00
commit c9c90c9732
No known key found for this signature in database
62 changed files with 334 additions and 228 deletions

View file

@ -27,7 +27,7 @@ import type { Settings } from "@api/Settings";
import { debounce } from "@shared/debounce";
import { localStorage } from "@utils/localStorage";
import { getStylusWebStoreUrl } from "@utils/web";
import { EXTENSION_BASE_URL } from "@utils/web-metadata";
import { EXTENSION_BASE_URL, metaReady, RENDERER_CSS_URL } from "@utils/web-metadata";
// listeners for ipc.on
const cssListeners = new Set<(css: string) => void>();
@ -55,7 +55,18 @@ window.VencordNative = {
native: {
getVersions: () => ({}),
openExternal: async (url) => void open(url, "_blank")
openExternal: async (url) => void open(url, "_blank"),
getRendererCss: async () => {
if (IS_USERSCRIPT)
// need to wait for next tick for _vcUserScriptRendererCss to be set
return Promise.resolve().then(() => window._vcUserScriptRendererCss);
await metaReady;
return fetch(RENDERER_CSS_URL)
.then(res => res.text());
},
onRendererCssUpdate: NOOP,
},
updater: {
@ -92,18 +103,20 @@ window.VencordNative = {
return;
}
const { getTheme, Theme } = require("@utils/discord");
win.baseUrl = EXTENSION_BASE_URL;
win.setCss = setCssDebounced;
win.getCurrentCss = () => VencordNative.quickCss.get();
win.getTheme = () =>
getTheme() === Theme.Light
? "vs-light"
: "vs-dark";
win.getTheme = this.getEditorTheme;
win.document.write(monacoHtmlLocal);
},
getEditorTheme: () => {
const { getTheme, Theme } = require("@utils/discord");
return getTheme() === Theme.Light
? "vs-light"
: "vs-dark";
}
},
settings: {

View file

@ -12,7 +12,7 @@ chrome.webRequest.onHeadersReceived.addListener(
({ responseHeaders, type, url }) => {
if (!responseHeaders) return;
if (type === "main_frame") {
if (type === "main_frame" && url.includes("discord.com")) {
// In main frame requests, the CSP needs to be removed to enable fetching of custom css
// as desired by the user
removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-security-policy");

View file

@ -2,20 +2,15 @@ if (typeof browser === "undefined") {
var browser = chrome;
}
const style = document.createElement("link");
style.type = "text/css";
style.rel = "stylesheet";
style.href = browser.runtime.getURL("dist/Equicord.css");
document.addEventListener(
"DOMContentLoaded",
() => {
document.body.insertAdjacentElement("afterend", style);
window.postMessage({
type: "vencord:meta",
meta: {
EXTENSION_VERSION: browser.runtime.getManifest().version,
EXTENSION_BASE_URL: browser.runtime.getURL(""),
RENDERER_CSS_URL: browser.runtime.getURL("dist/Equicord.css"),
}
});
},

View file

@ -15,7 +15,8 @@
]
},
"condition": {
"resourceTypes": ["main_frame", "sub_frame"]
"resourceTypes": ["main_frame", "sub_frame"],
"urlFilter": "||discord.com^"
}
},
{
@ -32,7 +33,7 @@
},
"condition": {
"resourceTypes": ["stylesheet"],
"urlFilter": "https://raw.githubusercontent.com/*"
"urlFilter": "||raw.githubusercontent.com^"
}
}
]

View file

@ -1,7 +1,7 @@
{
"name": "equicord",
"private": "true",
"version": "1.13.9.1",
"version": "1.13.11",
"description": "The other cutest Discord client mod",
"homepage": "https://github.com/Equicord/Equicord#readme",
"bugs": {

View file

@ -276,6 +276,7 @@ export interface UsernameUtils {
humanizeStatus: any;
}
// TODO: fix type
export class DisplayProfile {
userId: string;
banner?: string;

1
pnpm-lock.yaml generated
View file

@ -2143,7 +2143,6 @@ packages:
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
deprecated: |-
You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.
(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
qified@0.5.2:

View file

@ -185,14 +185,7 @@ async function buildExtension(target, files) {
}
const appendCssRuntime = readFile("dist/Equicord.user.css", "utf-8").then(content => {
const cssRuntime = `
;document.addEventListener("DOMContentLoaded", () => document.body.insertAdjacentElement("afterend",
Object.assign(document.createElement("style"), {
textContent: \`${content.replaceAll("`", "\\`")}\`,
id: "vencord-css-core"
})
), { once: true });
`;
const cssRuntime = `unsafeWindow._vcUserScriptRendererCss=\`${content.replaceAll("`", "\\`")}\``;
return appendFile("dist/Equicord.user.js", cssRuntime);
});

View file

@ -27,11 +27,11 @@ import { constants as FsConstants, readFileSync } from "fs";
import { access, readdir, readFile } from "fs/promises";
import { minify as minifyHtml } from "html-minifier-terser";
import { dirname, join, relative, resolve } from "path";
import { optimize as optimizeSvg } from "svgo";
import { fileURLToPath } from "url";
import { promisify } from "util";
import { getPluginTarget } from "../utils.mjs";
import { builtinModules } from "module";
const PackageJSON = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), "../../package.json"), "utf-8"));
@ -261,7 +261,7 @@ export const fileUrlPlugin = {
build.onLoad({ filter, namespace: "file-uri" }, async ({ pluginData: { path, uri } }) => {
const { searchParams } = new URL(uri);
const base64 = searchParams.has("base64");
const minify = IS_STANDALONE === true && searchParams.has("minify");
const minify = searchParams.has("minify");
const noTrim = searchParams.get("trim") === "false";
const encoding = base64 ? "base64" : "utf-8";
@ -283,6 +283,12 @@ export const fileUrlPlugin = {
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
});
} else if (path.endsWith(".svg")) {
content = optimizeSvg(await readFile(path, "utf-8"), {
datauri: base64 ? "base64" : void 0,
multipass: true,
floatPrecision: 2,
}).data;
} else if (/[mc]?[jt]sx?$/.test(path)) {
const res = await esbuild.build({
entryPoints: [path],
@ -294,7 +300,7 @@ export const fileUrlPlugin = {
throw new Error(`Don't know how to minify file type: ${path}`);
}
if (base64)
if (base64 && !content.startsWith("data:"))
content = Buffer.from(content).toString("base64");
}

View file

@ -28,7 +28,7 @@ export * as Webpack from "./webpack";
export * as WebpackPatcher from "./webpack/patchWebpack";
export { PlainSettings, Settings };
import { addVencordUiStyles } from "@components/css";
import { coreStyleRootNode, initStyles } from "@api/Styles";
import { openSettingsTabModal, UpdaterTab } from "@components/settings";
import { debounce } from "@shared/debounce";
import { IS_WINDOWS } from "@utils/constants";
@ -39,7 +39,7 @@ import { get as dsGet } from "./api/DataStore";
import { NotificationData, showNotification } from "./api/Notifications";
import { initPluginManager, PMLogger, startAllPlugins } from "./api/PluginManager";
import { PlainSettings, Settings, SettingsStore } from "./api/Settings";
import { getCloudSettings, putCloudSettings } from "./api/SettingsSync/cloudSync";
import { getCloudSettings, putCloudSettings, shouldCloudSync } from "./api/SettingsSync/cloudSync";
import { localStorage } from "./utils/localStorage";
import { relaunch } from "./utils/native";
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
@ -53,6 +53,10 @@ if (IS_REPORTER) {
async function syncSettings() {
// Check if cloud auth exists for current user before attempting sync
if (localStorage.Vencord_cloudSyncDirection === undefined) {
// by default, sync bi-directionally
localStorage.Vencord_cloudSyncDirection = "both";
}
const hasCloudAuth = await dsGet("Vencord_cloudSecret");
if (!hasCloudAuth) {
if (Settings.cloud.authenticated) {
@ -87,13 +91,12 @@ async function syncSettings() {
if (
Settings.cloud.settingsSync && // if it's enabled
Settings.cloud.authenticated // if cloud integrations are enabled
Settings.cloud.authenticated && // if cloud integrations are enabled
localStorage.Vencord_cloudSyncDirection !== "manual" // if we're not in manual mode
) {
if (localStorage.Vencord_settingsDirty) {
if (localStorage.Vencord_settingsDirty && shouldCloudSync("push")) {
await putCloudSettings();
delete localStorage.Vencord_settingsDirty;
} else if (await getCloudSettings(false)) {
// if we synchronized something (false means no sync)
} else if (shouldCloudSync("pull") && await getCloudSettings(false)) { // if we synchronized something (false means no sync)
// we show a notification here instead of allowing getCloudSettings() to show one to declutter the amount of
// potential notifications that might occur. getCloudSettings() will always send a notification regardless if
// there was an error to notify the user, but besides that we only want to show one notification instead of all
@ -108,9 +111,8 @@ async function syncSettings() {
}
const saveSettingsOnFrequentAction = debounce(async () => {
if (Settings.cloud.settingsSync && Settings.cloud.authenticated) {
if (Settings.cloud.settingsSync && Settings.cloud.authenticated && shouldCloudSync("push")) {
await putCloudSettings();
delete localStorage.Vencord_settingsDirty;
}
}, 60_000);
@ -192,16 +194,15 @@ async function init() {
}
initPluginManager();
initStyles();
startAllPlugins(StartAt.Init);
init();
document.addEventListener("DOMContentLoaded", () => {
addVencordUiStyles();
startAllPlugins(StartAt.DOMContentLoaded);
// FIXME
if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && IS_WINDOWS) {
createAndAppendStyle("vencord-native-titlebar-style").textContent = "[class*=titleBar]{display: none!important}";
createAndAppendStyle("vencord-native-titlebar-style", coreStyleRootNode).textContent = "[class*=titleBar]{display: none!important}";
}
}, { once: true });

View file

@ -5,13 +5,13 @@
*/
import type { Settings } from "@api/Settings";
import { CspRequestResult } from "@main/csp/manager";
import { PluginIpcMappings } from "@main/ipcPlugins";
import type { CspRequestResult } from "@main/csp/manager";
import type { PluginIpcMappings } from "@main/ipcPlugins";
import { IpcEvents } from "@shared/IpcEvents";
import { IpcRes } from "@utils/types";
import { ipcRenderer } from "electron";
import type { IpcRes } from "@utils/types";
import { ipcRenderer } from "electron/renderer";
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
export function invoke<T = any>(event: IpcEvents, ...args: any[]) {
return ipcRenderer.invoke(event, ...args) as Promise<T>;
}
@ -31,8 +31,12 @@ for (const [plugin, methods] of Object.entries(pluginIpcMap)) {
export default {
themes: {
uploadTheme: (fileName: string, fileData: string) => invoke<void>(IpcEvents.UPLOAD_THEME, fileName, fileData),
deleteTheme: (fileName: string) => invoke<void>(IpcEvents.DELETE_THEME, fileName),
uploadTheme: async (fileName: string, fileData: string): Promise<void> => {
throw new Error("uploadTheme is WEB only");
},
deleteTheme: async (fileName: string): Promise<void> => {
throw new Error("deleteTheme is WEB only");
},
getThemesDir: () => invoke<string>(IpcEvents.GET_THEMES_DIR),
getThemesList: () => invoke<Array<{ fileName: string; content: string; }>>(IpcEvents.GET_THEMES_LIST),
getThemeData: (fileName: string) => invoke<string | undefined>(IpcEvents.GET_THEME_DATA, fileName),
@ -70,11 +74,18 @@ export default {
openFile: () => invoke<void>(IpcEvents.OPEN_QUICKCSS),
openEditor: () => invoke<void>(IpcEvents.OPEN_MONACO_EDITOR),
getEditorTheme: () => sendSync<string>(IpcEvents.GET_MONACO_THEME),
},
native: {
getVersions: () => process.versions as Partial<NodeJS.ProcessVersions>,
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url),
getRendererCss: () => invoke<string>(IpcEvents.GET_RENDERER_CSS),
onRendererCssUpdate: (cb: (newCss: string) => void) => {
if (!IS_DEV) return;
ipcRenderer.on(IpcEvents.RENDERER_CSS_UPDATE, (_e, newCss: string) => cb(newCss));
}
},
csp: {

View file

@ -50,7 +50,8 @@ export interface ProfileBadge {
key?: string;
/**
* Allows dynamically returning multiple badges
* Allows dynamically returning multiple badges.
* May call hooks but then you must not use shouldShow
*/
getBadges?(userInfo: BadgeUserArgs): ProfileBadge[];
}

View file

@ -18,11 +18,11 @@
import * as DataStore from "@api/DataStore";
import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText";
import { Flex } from "@components/Flex";
import { Paragraph } from "@components/Paragraph";
import { openNotificationSettingsModal } from "@components/settings/tabs/vencord/NotificationSettings";
import { classNameFactory } from "@utils/css";
import { closeModal, ModalCloseButton, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react";
import { Alerts, Button, ListScrollerThin, React, Timestamp, useEffect, useReducer, useState } from "@webpack/common";

View file

@ -6,6 +6,7 @@
import { showNotification } from "@api/Notifications";
import { PlainSettings, Settings } from "@api/Settings";
import { localStorage } from "@utils/localStorage";
import { Logger } from "@utils/Logger";
import { relaunch } from "@utils/native";
import { openUserSettingsPanel } from "@webpack/common";
@ -16,6 +17,12 @@ import { exportSettings, importSettings } from "./offline";
const logger = new Logger("SettingsSync:Cloud", "#39b7e0");
export function shouldCloudSync(direction: "push" | "pull") {
const localDirection = localStorage.Vencord_cloudSyncDirection;
return localDirection === direction || localDirection === "both";
}
export async function putCloudSettings(manual?: boolean) {
const settings = await exportSettings({ syncDataStore: false, minify: true });
@ -52,6 +59,8 @@ export async function putCloudSettings(manual?: boolean) {
noPersist: true,
});
}
delete localStorage.Vencord_settingsDirty;
} catch (e: any) {
logger.error("Failed to sync up", e);
showNotification({
@ -151,6 +160,8 @@ export async function getCloudSettings(shouldNotify = true, force = false) {
noPersist: true
});
delete localStorage.Vencord_settingsDirty;
return true;
} catch (e: any) {
logger.error("Failed to sync down", e);

View file

@ -16,6 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { generateTextCss } from "@components/BaseText";
import { generateMarginCss } from "@components/margins";
import { classNameFactory as _classNameFactory, classNameToSelector, createAndAppendStyle } from "@utils/css";
// Backwards compat for Vesktop
/** @deprecated Import this from `@utils/css` instead */
export const classNameFactory = _classNameFactory;
export interface Style {
name: string;
source: string;
@ -25,6 +33,57 @@ export interface Style {
export const styleMap = window.VencordStyles ??= new Map();
export const vencordRootNode = document.createElement("vencord-root");
/**
* Houses all Vencord core styles. This includes all imported css files
*/
export const coreStyleRootNode = document.createElement("vencord-styles");
/**
* Houses all plugin specific managed styles
*/
export const managedStyleRootNode = document.createElement("vencord-managed-styles");
/**
* Houses the user's themes and quick css
*/
export const userStyleRootNode = document.createElement("vencord-user-styles");
vencordRootNode.style.display = "none";
vencordRootNode.append(coreStyleRootNode, managedStyleRootNode, userStyleRootNode);
export function initStyles() {
const osValuesNode = createAndAppendStyle("vencord-os-theme-values", coreStyleRootNode);
createAndAppendStyle("vencord-text", coreStyleRootNode).textContent = generateTextCss();
const rendererCssNode = createAndAppendStyle("vencord-css-core", coreStyleRootNode);
const vesktopCssNode = IS_VESKTOP ? createAndAppendStyle("vesktop-css-core", coreStyleRootNode) : null;
createAndAppendStyle("vencord-margins", coreStyleRootNode).textContent = generateMarginCss();
VencordNative.native.getRendererCss().then(css => rendererCssNode.textContent = css);
if (IS_DEV) {
VencordNative.native.onRendererCssUpdate(newCss => {
rendererCssNode.textContent = newCss;
});
}
if (IS_VESKTOP && VesktopNative.app.getRendererCss) {
VesktopNative.app.getRendererCss().then(css => vesktopCssNode!.textContent = css);
VesktopNative.app.onRendererCssUpdate(newCss => {
vesktopCssNode!.textContent = newCss;
});
}
VencordNative.themes.getSystemValues().then(values => {
const variables = Object.entries(values)
.filter(([, v]) => !!v)
.map(([k, v]) => `--${k}: ${v};`)
.join("");
osValuesNode.textContent = `:root{${variables}}`;
});
}
document.addEventListener("DOMContentLoaded", () => {
document.documentElement.append(vencordRootNode);
}, { once: true });
export function requireStyle(name: string) {
const style = styleMap.get(name);
if (!style) throw new Error(`Style "${name}" does not exist`);
@ -53,7 +112,7 @@ export function enableStyle(name: string) {
}
compileStyle(style);
document.head.appendChild(style.dom);
managedStyleRootNode.appendChild(style.dom);
return true;
}
@ -135,31 +194,4 @@ export const compileStyle = (style: Style) => {
});
};
/**
* @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> | false | null | undefined | 0 | "";
/**
* @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 (arg && typeof arg === "string") classNames.add(arg);
else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name));
else if (arg && typeof arg === "object") Object.entries(arg).forEach(([name, value]) => value && classNames.add(name));
}
return Array.from(classNames, name => prefix + name).join(" ");
};

View file

@ -20,23 +20,15 @@ import { Settings, SettingsStore } from "@api/Settings";
import { createAndAppendStyle } from "@utils/css";
import { ThemeStore } from "@vencord/discord-types";
import { userStyleRootNode } from "./Styles";
let style: HTMLStyleElement;
let themesStyle: HTMLStyleElement;
async function initSystemValues() {
const values = await VencordNative.themes.getSystemValues();
const variables = Object.entries(values)
.filter(([, v]) => v !== "#")
.map(([k, v]) => `--${k}: ${v};`)
.join("");
createAndAppendStyle("vencord-os-theme-values").textContent = `:root{${variables}}`;
}
async function toggle(isEnabled: boolean) {
if (!style) {
if (isEnabled) {
style = createAndAppendStyle("vencord-custom-css");
style = createAndAppendStyle("vencord-custom-css", userStyleRootNode);
VencordNative.quickCss.addChangeListener(css => {
style.textContent = css;
// At the time of writing this, changing textContent resets the disabled state
@ -49,7 +41,7 @@ async function toggle(isEnabled: boolean) {
}
async function initThemes() {
themesStyle ??= createAndAppendStyle("vencord-themes");
themesStyle ??= createAndAppendStyle("vencord-themes", userStyleRootNode);
const { enabledThemeLinks, enabledThemes } = Settings;
@ -89,7 +81,6 @@ async function initThemes() {
document.addEventListener("DOMContentLoaded", () => {
if (IS_USERSCRIPT) return;
initSystemValues();
initThemes();
toggle(Settings.useQuickCss);

View file

@ -6,7 +6,7 @@
import "./BaseText.css";
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
import { classes } from "@utils/misc";
import type { Text as DiscordText } from "@vencord/discord-types";
import type { ComponentPropsWithoutRef, ReactNode } from "react";

View file

@ -124,11 +124,11 @@
}
.vc-btn-positive {
background-color: var(--redesign-button-positive-background);
background-color: var(--redesign-button-positive-background, var(--green-430));
color: var(--white);
&:hover {
background-color: var(--redesign-button-positive-pressed-background);
background-color: var(--redesign-button-positive-pressed-background, var(--green-460));
}
}

View file

@ -6,7 +6,7 @@
import "./Button.css";
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
import { classes } from "@utils/misc";
import type { Button as DiscordButton } from "@vencord/discord-types";
import type { ComponentPropsWithRef } from "react";

View file

@ -6,7 +6,7 @@
import "./Card.css";
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
import { classes } from "@utils/misc";
import { ComponentPropsWithRef } from "react";

View file

@ -686,3 +686,31 @@ export function MagnifyingGlassIcon(props: IconProps) {
</Icon>
);
}
export function CloudDownloadIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M6.5 20Q4.22 20 2.61 18.43 1 16.85 1 14.58 1 12.63 2.17 11.1 3.35 9.57 5.25 9.15 5.83 7.13 7.39 5.75 8.95 4.38 11 4.08V12.15L9.4 10.6L8 12L12 16L16 12L14.6 10.6L13 12.15V4.08Q15.58 4.43 17.29 6.39 19 8.35 19 11 20.73 11.2 21.86 12.5 23 13.78 23 15.5 23 17.38 21.69 18.69 20.38 20 18.5 20Z"
/>
</Icon>
);
}
export function CloudUploadIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M11 20H6.5Q4.22 20 2.61 18.43 1 16.85 1 14.58 1 12.63 2.17 11.1 3.35 9.57 5.25 9.15 5.88 6.85 7.75 5.43 9.63 4 12 4 14.93 4 16.96 6.04 19 8.07 19 11 20.73 11.2 21.86 12.5 23 13.78 23 15.5 23 17.38 21.69 18.69 20.38 20 18.5 20H13V12.85L14.6 14.4L16 13L12 9L8 13L9.4 14.4L11 12.85Z"
/>
</Icon>
);
}

View file

@ -18,7 +18,7 @@
import "./Switch.css";
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
import { classes } from "@utils/misc";
import { useState } from "@webpack/common";
import type { FocusEvent } from "react";

View file

@ -1,15 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { createAndAppendStyle } from "@utils/css";
import { generateTextCss } from "./BaseText";
import { generateMarginCss } from "./margins";
export function addVencordUiStyles() {
createAndAppendStyle("vencord-text", document.head).textContent = generateTextCss();
createAndAppendStyle("vencord-margins").textContent = generateMarginCss();
}

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
const marginCls = classNameFactory("vc-margin-");

View file

@ -18,10 +18,10 @@
import "./AddonCard.css";
import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText";
import { AddonBadge } from "@components/settings/PluginBadge";
import { Switch } from "@components/Switch";
import { classNameFactory } from "@utils/css";
import { Tooltip, useRef } from "@webpack/common";
import type { MouseEventHandler, ReactNode } from "react";

View file

@ -6,8 +6,8 @@
import "./QuickAction.css";
import { classNameFactory } from "@api/Styles";
import { Card } from "@components/Card";
import { classNameFactory } from "@utils/css";
import type { ComponentType, PropsWithChildren, ReactNode } from "react";
const cl = classNameFactory("vc-settings-quickActions-");

View file

@ -18,11 +18,11 @@
import "./SpecialCard.css";
import { classNameFactory } from "@api/Styles";
import { Card } from "@components/Card";
import { Divider } from "@components/Divider";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { classNameFactory } from "@utils/css";
import { Clickable } from "@webpack/common";
import type { PropsWithChildren } from "react";

View file

@ -7,12 +7,12 @@
import "./ContributorModal.css";
import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { HeadingPrimary } from "@components/Heading";
import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph";
import { EquicordDevsById, VencordDevsById } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { fetchUserProfile } from "@utils/discord";
import { pluralise } from "@utils/misc";
import { ModalContent, ModalFooter, ModalRoot, openModal } from "@utils/modal";

View file

@ -20,7 +20,6 @@ import "./PluginModal.css";
import { generateId } from "@api/Commands";
import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
@ -28,6 +27,7 @@ import { Flex } from "@components/Flex";
import { Paragraph } from "@components/Paragraph";
import { debounce } from "@shared/debounce";
import { gitRemote } from "@shared/vencordUserAgent";
import { classNameFactory } from "@utils/css";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
import { classes, isObjectEmpty } from "@utils/misc";

View file

@ -9,12 +9,12 @@ import "./UIElements.css";
import { ChatBarButtonMap } from "@api/ChatButtons";
import { MessagePopoverButtonMap } from "@api/MessagePopover";
import { SettingsPluginUiElements, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText";
import { Card } from "@components/Card";
import { PlaceholderIcon } from "@components/Icons";
import { Paragraph } from "@components/Paragraph";
import { Switch } from "@components/Switch";
import { classNameFactory } from "@utils/css";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";

View file

@ -4,8 +4,8 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText";
import { classNameFactory } from "@utils/css";
import { classes } from "@utils/misc";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { DefinedSettings, PluginOptionBase } from "@utils/types";

View file

@ -21,7 +21,6 @@ import "./styles.css";
import * as DataStore from "@api/DataStore";
import { isPluginEnabled, stopPlugin } from "@api/PluginManager";
import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Button } from "@components/Button";
import { Card } from "@components/Card";
import { Divider } from "@components/Divider";
@ -31,6 +30,7 @@ import { Paragraph } from "@components/Paragraph";
import { SettingsTab } from "@components/settings";
import { debounce } from "@shared/debounce";
import { ChangeList } from "@utils/ChangeList";
import { classNameFactory } from "@utils/css";
import { isTruthy } from "@utils/guards";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";

View file

@ -72,6 +72,7 @@
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1em;
margin-top: 16px;
}
.vc-cloud-erase-data-danger-btn {
@ -79,6 +80,16 @@
background-color: var(--redesign-button-danger-background);
}
.vc-cloud-icon-with-button {
display: flex;
gap: 0.5em;
padding-inline: 0.5em 1em;
}
.vc-cloud-button-icon {
height: 1.25em;
}
.vc-settings-modal {
padding: 1.5em !important;
}

View file

@ -19,12 +19,12 @@
import "./spotifyStyles.css";
import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex";
import { CopyIcon, ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
import { Paragraph } from "@components/Paragraph";
import { Span } from "@components/Span";
import { debounce } from "@shared/debounce";
import { classNameFactory } from "@utils/css";
import { copyWithToast, openImageModal } from "@utils/discord";
import { classes } from "@utils/misc";
import { ContextMenuApi, FluxDispatcher, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common";

View file

@ -22,16 +22,18 @@ import "./settings";
import { debounce } from "@shared/debounce";
import { IpcEvents } from "@shared/IpcEvents";
import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron";
import { BrowserWindow, ipcMain, nativeTheme, shell, systemPreferences } from "electron";
import monacoHtml from "file://monacoWin.html?minify&base64";
import { FSWatcher, mkdirSync, watch, writeFileSync } from "fs";
import { FSWatcher, mkdirSync, readFileSync, watch, writeFileSync } from "fs";
import { open, readdir, readFile, unlink } from "fs/promises";
import { join, normalize } from "path";
import { registerCspIpcHandlers } from "./csp/manager";
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, THEMES_DIR } from "./utils/constants";
import { ALLOWED_PROTOCOLS, QUICK_CSS_PATH, SETTINGS_DIR, THEMES_DIR } from "./utils/constants";
import { makeLinksOpenExternally } from "./utils/externalLinks";
const RENDERER_CSS_PATH = join(__dirname, IS_VESKTOP ? "vencordDesktopRenderer.css" : "renderer.css");
mkdirSync(THEMES_DIR, { recursive: true });
registerCspIpcHandlers();
@ -43,12 +45,8 @@ export function ensureSafePath(basePath: string, path: string) {
return normalizedPath.startsWith(normalizedBasePath) ? normalizedPath : null;
}
async function readCss() {
try {
return await readFile(QUICKCSS_PATH, "utf-8");
} catch {
return "";
}
function readCss() {
return readFile(QUICK_CSS_PATH, "utf-8").catch(() => "");
}
async function listThemes(): Promise<{ fileName: string; content: string; }[]> {
@ -67,7 +65,7 @@ function getThemeData(fileName: string) {
return readFile(safePath, "utf-8");
}
ipcMain.handle(IpcEvents.OPEN_QUICKCSS, () => shell.openPath(QUICKCSS_PATH));
ipcMain.handle(IpcEvents.OPEN_QUICKCSS, () => shell.openPath(QUICK_CSS_PATH));
ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => {
try {
@ -83,7 +81,7 @@ ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => {
ipcMain.handle(IpcEvents.GET_QUICK_CSS, () => readCss());
ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) =>
writeFileSync(QUICKCSS_PATH, css)
writeFileSync(QUICK_CSS_PATH, css)
);
ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR);
@ -94,33 +92,52 @@ ipcMain.handle(IpcEvents.DELETE_THEME, (_, fileName) => {
if (!safePath) return Promise.reject(`Unsafe path ${fileName}`);
return unlink(safePath);
});
ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => ({
// win & mac only
"os-accent-color": `#${systemPreferences.getAccentColor?.() || ""}`
}));
ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => {
let accentColor = systemPreferences.getAccentColor?.() ?? "";
if (accentColor.length && accentColor[0] !== "#") {
accentColor = `#${accentColor}`;
}
return {
"os-accent-color": accentColor
};
});
ipcMain.handle(IpcEvents.OPEN_THEMES_FOLDER, () => shell.openPath(THEMES_DIR));
ipcMain.handle(IpcEvents.OPEN_SETTINGS_FOLDER, () => shell.openPath(SETTINGS_DIR));
export function initIpc(mainWindow: BrowserWindow) {
ipcMain.handle(IpcEvents.INIT_FILE_WATCHERS, ({ sender }) => {
let quickCssWatcher: FSWatcher | undefined;
let rendererCssWatcher: FSWatcher | undefined;
open(QUICKCSS_PATH, "a+").then(fd => {
open(QUICK_CSS_PATH, "a+").then(fd => {
fd.close();
quickCssWatcher = watch(QUICKCSS_PATH, { persistent: false }, debounce(async () => {
mainWindow.webContents.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss());
quickCssWatcher = watch(QUICK_CSS_PATH, { persistent: false }, debounce(async () => {
sender.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss());
}, 50));
}).catch(() => { });
const themesWatcher = watch(THEMES_DIR, { persistent: false }, debounce(() => {
mainWindow.webContents.postMessage(IpcEvents.THEME_UPDATE, void 0);
sender.postMessage(IpcEvents.THEME_UPDATE, void 0);
}));
mainWindow.once("closed", () => {
if (IS_DEV) {
rendererCssWatcher = watch(RENDERER_CSS_PATH, { persistent: false }, async () => {
sender.postMessage(IpcEvents.RENDERER_CSS_UPDATE, await readFile(RENDERER_CSS_PATH, "utf-8"));
});
}
sender.once("destroyed", () => {
quickCssWatcher?.close();
themesWatcher.close();
rendererCssWatcher?.close();
});
}
});
ipcMain.on(IpcEvents.GET_MONACO_THEME, e => {
e.returnValue = nativeTheme.shouldUseDarkColors ? "vs-dark" : "vs-light";
});
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
const title = "Equicord QuickCSS Editor";
@ -146,3 +163,11 @@ ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
await win.loadURL(`data:text/html;base64,${monacoHtml}`);
});
ipcMain.handle(IpcEvents.GET_RENDERER_CSS, () => readFile(RENDERER_CSS_PATH, "utf-8"));
if (IS_DISCORD_DESKTOP) {
ipcMain.on(IpcEvents.PRELOAD_GET_RENDERER_JS, e => {
e.returnValue = readFileSync(join(__dirname, "renderer.js"), "utf-8");
});
}

View file

@ -20,7 +20,6 @@ import { onceDefined } from "@shared/onceDefined";
import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron";
import { dirname, join } from "path";
import { initIpc } from "./ipcMain";
import { RendererSettings } from "./settings";
import { IS_VANILLA } from "./utils/constants";
@ -107,8 +106,6 @@ if (!IS_VANILLA) {
// Disable the Electron call entirely so that Discord can't dynamically change the size
this.setMinimumSize = (width: number, height: number) => { };
}
initIpc(this);
} else super(options);
}
}

View file

@ -30,7 +30,7 @@ export const DATA_DIR = process.env.EQUICORD_USER_DATA_DIR ?? (
export const SETTINGS_DIR = join(DATA_DIR, "settings");
export const THEMES_DIR = join(DATA_DIR, "themes");
export const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css");
export const QUICK_CSS_PATH = join(SETTINGS_DIR, "quickCss.css");
export const SETTINGS_FILE = join(SETTINGS_DIR, "settings.json");
export const NATIVE_SETTINGS_FILE = join(SETTINGS_DIR, "native-settings.json");
export const DEV_MIGRATED = join(SETTINGS_DIR, "migration");

View file

@ -26,11 +26,8 @@ import { copyWithToast } from "@utils/discord";
import { Logger } from "@utils/Logger";
import { shouldShowContributorBadge, shouldShowEquicordContributorBadge } from "@utils/misc";
import definePlugin from "@utils/types";
import { User } from "@vencord/discord-types";
import { ContextMenuApi, Menu, Toasts, UserStore } from "@webpack/common";
import { ContextMenuApi, Forms, Menu, Toasts, UserStore } from "@webpack/common";
import Plugins, { PluginMeta } from "~plugins";
import { EquicordDonorModal, VencordDonorModal } from "./modals";
const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64";
@ -131,16 +128,13 @@ export default definePlugin({
authors: [Devs.Megu, Devs.Ven, Devs.TheSun],
required: true,
patches: [
{
find: ".MODAL]:26",
replacement: {
match: /(?=;return 0===(\i)\.length\?)(?<=(\i)\.useMemo.+?)/,
replace: ";$1=$2.useMemo(()=>[...$self.getBadges(arguments[0].displayProfile),...$1],[$1])"
}
},
{
find: "#{intl::PROFILE_USER_BADGES}",
replacement: [
{
match: /(?<=\{[^}]*?)badges:\i(?=[^}]*?}=(\i))/,
replace: "_$&=$self.useBadges($1.displayProfile).concat($1.badges)"
},
{
match: /alt:" ","aria-hidden":!0,src:.{0,50}(\i).iconSrc/,
replace: "...$1.props,$&"
@ -155,13 +149,6 @@ export default definePlugin({
replace: "...$self.getBadgeMouseEventHandlers($1),$&"
}
]
},
{
find: "profileCardUsernameRow,children:",
replacement: {
match: /badges:(\i)(?<=displayProfile:(\i).+?)/,
replace: "badges:[...$self.getBadges($2),...$1]"
}
}
],
@ -197,15 +184,14 @@ export default definePlugin({
clearInterval(intervalId);
},
getBadges(props: { userId: string; user?: User; guildId: string; }) {
if (!props) return [];
// doesn't use hooks itself, but some plugins might do so in their getBadges function
useBadges(profile: { userId: string; guildId: string; }) {
if (!profile) return [];
try {
props.userId ??= props.user?.id!;
return _getBadges(props);
return _getBadges(profile);
} catch (e) {
new Logger("BadgeAPI#hasBadges").error(e);
new Logger("BadgeAPI#useBadges").error(e);
return [];
}
},

View file

@ -4,12 +4,12 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classNameFactory } from "@api/Styles";
import { ErrorCard } from "@components/ErrorCard";
import { HeadingPrimary, HeadingSecondary } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { relativeLuminance } from "@plugins/clientTheme/utils/colorUtils";
import { createOrUpdateThemeColorVars } from "@plugins/clientTheme/utils/styleUtils";
import { classNameFactory } from "@utils/css";
import { Margins } from "@utils/margins";
import { findByCodeLazy, findStoreLazy } from "@webpack";
import { Button, ColorPicker, ThemeStore, useStateFromStores } from "@webpack/common";

View file

@ -7,11 +7,11 @@
import "./settings.css";
import { isPluginEnabled } from "@api/PluginManager";
import { classNameFactory } from "@api/Styles";
import { Divider } from "@components/Divider";
import { Heading } from "@components/Heading";
import { resolveError } from "@components/settings/tabs/plugins/components/Common";
import { debounce } from "@shared/debounce";
import { classNameFactory } from "@utils/css";
import { ActivityType } from "@vencord/discord-types/enums";
import { Select, Text, TextInput, useState } from "@webpack/common";

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
import { extractAndLoadChunksLazy, findByPropsLazy } from "@webpack";
export const cl = classNameFactory("vc-decor-");

View file

@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { settings } from "@plugins/imageZoom";
import { ELEMENT_ID } from "@plugins/imageZoom/constants";
import { waitFor } from "@plugins/imageZoom/utils/waitFor";
import { classNameFactory } from "@utils/css";
import { FluxDispatcher, useLayoutEffect, useMemo, useRef, useState } from "@webpack/common";
interface Vec2 {

View file

@ -19,9 +19,9 @@
import "./style.css";
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import definePlugin, { OptionType } from "@utils/types";
import { FluxStore } from "@vencord/discord-types";
import { findStoreLazy } from "@webpack";

View file

@ -4,9 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText";
import ErrorBoundary from "@components/ErrorBoundary";
import { classNameFactory } from "@utils/css";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
import { Guild, GuildMember, Role } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack";
import { GuildRoleStore } from "@webpack/common";

View file

@ -4,12 +4,12 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText";
import { Divider } from "@components/Divider";
import { Heading } from "@components/Heading";
import { DEFAULT_COLOR, SWATCHES } from "@plugins/pinDms/constants";
import { categoryLen, createCategory, getCategory } from "@plugins/pinDms/data";
import { classNameFactory } from "@utils/css";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal";
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
import { Button, ColorPicker, TextInput, Toasts, useMemo, useState } from "@webpack/common";

View file

@ -0,0 +1,3 @@
<svg height="20" width="20" viewBox="0 0 24 24" fill="#123456" xmlns="http://www.w3.org/2000/svg">
<path d="M4 2.5c-1.103 0-2 .897-2 2v11c0 1.104.897 2 2 2h7v2H7v2h10v-2h-4v-2h7c1.103 0 2-.896 2-2v-11c0-1.103-.897-2-2-2H4Zm16 2v9H4v-9h16Z" />
</svg>

After

Width:  |  Height:  |  Size: 254 B

View file

@ -0,0 +1,3 @@
<svg height="20" width="20" viewBox="0 0 50 50" fill="#123456" xmlns="http://www.w3.org/2000/svg">
<path d="M14.8 2.7 9 3.1V47h3.3c1.7 0 6.2.3 10 .7l6.7.6V2l-4.2.2c-2.4.1-6.9.3-10 .5zm1.8 6.4c1 1.7-1.3 3.6-2.7 2.2C12.7 10.1 13.5 8 15 8c.5 0 1.2.5 1.6 1.1zM16 33c0 6-.4 10-1 10s-1-4-1-10 .4-10 1-10 1 4 1 10zm15-8v23.3l3.8-.7c2-.3 4.7-.6 6-.6H43V3h-2.2c-1.3 0-4-.3-6-.6L31 1.7V25z" />
</svg>

After

Width:  |  Height:  |  Size: 395 B

View file

@ -0,0 +1,3 @@
<svg height="17" width="17" viewBox="0 0 1000 1500" fill="#123456" xmlns="http://www.w3.org/2000/svg">
<path d="M 187 0 L 813 0 C 916.277 0 1000 83.723 1000 187 L 1000 1313 C 1000 1416.277 916.277 1500 813 1500 L 187 1500 C 83.723 1500 0 1416.277 0 1313 L 0 187 C 0 83.723 83.723 0 187 0 Z M 125 1000 L 875 1000 L 875 250 L 125 250 Z M 500 1125 C 430.964 1125 375 1180.964 375 1250 C 375 1319.036 430.964 1375 500 1375 C 569.036 1375 625 1319.036 625 1250 C 625 1180.964 569.036 1125 500 1125 Z" />
</svg>

After

Width:  |  Height:  |  Size: 510 B

View file

@ -0,0 +1,3 @@
<svg height="20" width="20" viewBox="0 0 24 24" fill="#123456" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93Zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39Z" />
</svg>

After

Width:  |  Height:  |  Size: 424 B

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
import { Toasts, UserStore } from "@webpack/common";
import { Auth } from "./auth";

View file

@ -20,10 +20,10 @@ import "./styles.css";
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Heading, HeadingPrimary } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { Devs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { getTheme, insertTextIntoChatInputBox, Theme } from "@utils/discord";
import { Margins } from "@utils/margins";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";

View file

@ -6,7 +6,7 @@
import "./styles.css";
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { getGuildAcronym, openImageModal, openUserProfile } from "@utils/discord";

View file

@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { classNameFactory } from "@api/Styles";
import { resolveLang } from "@plugins/shikiCodeblocks.desktop/api/languages";
import { HighlighterProps } from "@plugins/shikiCodeblocks.desktop/components/Highlighter";
import { HljsSetting } from "@plugins/shikiCodeblocks.desktop/types";
import { classNameFactory } from "@utils/css";
import { hljs } from "@webpack/common";
export const cl = classNameFactory("vc-shiki-");

View file

@ -19,9 +19,9 @@
import "./style.css";
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { classes } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import type { Channel, Role } from "@vencord/discord-types";

View file

@ -19,7 +19,7 @@
import "./styles.css";
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
import { BaseText } from "@components/BaseText";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
import { onlyOnce } from "@utils/onlyOnce";
import { PluginNative } from "@utils/types";
import { showToast, Toasts } from "@webpack/common";

View file

@ -5,7 +5,7 @@
*/
import { isPluginEnabled } from "@api/PluginManager";
import { classNameFactory } from "@api/Styles";
import { classNameFactory } from "@utils/css";
import { BaseText } from "@components/BaseText";
import ErrorBoundary from "@components/ErrorBoundary";
import ShowHiddenChannelsPlugin from "@plugins/showHiddenChannels";

View file

@ -17,48 +17,25 @@
*/
import { debounce } from "@shared/debounce";
import { contextBridge, webFrame } from "electron";
import { readFileSync, watch } from "fs";
import { join } from "path";
import { IpcEvents } from "@shared/IpcEvents";
import { contextBridge, webFrame } from "electron/renderer";
import VencordNative from "./VencordNative";
import VencordNative, { invoke, sendSync } from "./VencordNative";
contextBridge.exposeInMainWorld("VencordNative", VencordNative);
// Discord
if (location.protocol !== "data:") {
// #region cssInsert
const rendererCss = join(__dirname, "renderer.css");
const style = document.createElement("style");
style.id = "vencord-css-core";
style.textContent = readFileSync(rendererCss, "utf-8");
if (document.readyState === "complete") {
document.documentElement.appendChild(style);
} else {
document.addEventListener("DOMContentLoaded", () => document.documentElement.appendChild(style), {
once: true
});
}
if (IS_DEV) {
// persistent means keep process running if watcher is the only thing still running
// which we obviously don't want
watch(rendererCss, { persistent: false }, () => {
document.getElementById("vencord-css-core")!.textContent = readFileSync(rendererCss, "utf-8");
});
}
// #endregion
invoke(IpcEvents.INIT_FILE_WATCHERS);
if (IS_DISCORD_DESKTOP) {
webFrame.executeJavaScript(readFileSync(join(__dirname, "renderer.js"), "utf-8"));
webFrame.executeJavaScript(sendSync<string>(IpcEvents.PRELOAD_GET_RENDERER_JS));
// Not supported in sandboxed preload scripts but Discord doesn't support it either so who cares
require(process.env.DISCORD_PRELOAD!);
}
} // Monaco popout
else {
contextBridge.exposeInMainWorld("setCss", debounce(VencordNative.quickCss.set));
contextBridge.exposeInMainWorld("getCurrentCss", VencordNative.quickCss.get);
// shrug
contextBridge.exposeInMainWorld("getTheme", () => "vs-dark");
contextBridge.exposeInMainWorld("getTheme", VencordNative.quickCss.getEditorTheme);
}

View file

@ -17,8 +17,9 @@
*/
export const enum IpcEvents {
INIT_FILE_WATCHERS = "VencordInitFileWatchers",
QUICK_CSS_UPDATE = "VencordQuickCssUpdate",
THEME_UPDATE = "VencordThemeUpdate",
OPEN_QUICKCSS = "VencordOpenQuickCss",
GET_QUICK_CSS = "VencordGetQuickCss",
SET_QUICK_CSS = "VencordSetQuickCss",
UPLOAD_THEME = "VencordUploadTheme",
@ -30,23 +31,24 @@ export const enum IpcEvents {
GET_SETTINGS_DIR = "VencordGetSettingsDir",
GET_SETTINGS = "VencordGetSettings",
SET_SETTINGS = "VencordSetSettings",
THEME_UPDATE = "VencordThemeUpdate",
OPEN_EXTERNAL = "VencordOpenExternal",
OPEN_QUICKCSS = "VencordOpenQuickCss",
GET_UPDATES = "VencordGetUpdates",
GET_REPO = "VencordGetRepo",
UPDATE = "VencordUpdate",
BUILD = "VencordBuild",
OPEN_MONACO_EDITOR = "VencordOpenMonacoEditor",
GET_MONACO_THEME = "VencordGetMonacoTheme",
GET_PLUGIN_IPC_METHOD_MAP = "VencordGetPluginIpcMethodMap",
OPEN_IN_APP__RESOLVE_REDIRECT = "VencordOIAResolveRedirect",
VOICE_MESSAGES_READ_RECORDING = "VencordVMReadRecording",
CSP_IS_DOMAIN_ALLOWED = "VencordCspIsDomainAllowed",
CSP_REMOVE_OVERRIDE = "VencordCspRemoveOverride",
CSP_REQUEST_ADD_OVERRIDE = "VencordCspRequestAddOverride",
OPEN_THEMES_FOLDER = "VencordOpenThemesFolder",
OPEN_SETTINGS_FOLDER = "VencordOpenSettingsFolder",
GET_RENDERER_CSS = "VencordGetRendererCss",
RENDERER_CSS_UPDATE = "VencordRendererCssUpdate",
PRELOAD_GET_RENDERER_JS = "VencordPreloadGetRendererJs",
}

View file

@ -4,9 +4,32 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export function createAndAppendStyle(id: string, target = document.documentElement) {
export function createAndAppendStyle(id: string, target: HTMLElement) {
const style = document.createElement("style");
style.id = id;
target.append(style);
return style;
}
export const classNameToSelector = (name: string, prefix = "") => name.split(" ").map(n => `.${prefix}${n}`).join("");
export type ClassNameFactoryArg = string | string[] | Record<string, unknown> | false | null | undefined | 0 | "";
/**
* @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 (arg && typeof arg === "string") classNames.add(arg);
else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name));
else if (arg && typeof arg === "object") Object.entries(arg).forEach(([name, value]) => value && classNames.add(name));
}
return Array.from(classNames, name => prefix + name).join(" ");
};

View file

@ -4,14 +4,19 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export let EXTENSION_BASE_URL: string;
export let EXTENSION_VERSION: string;
export let EXTENSION_BASE_URL: string;
export let RENDERER_CSS_URL: string;
let resolveMetaReady: Function;
export const metaReady = new Promise<void>(res => resolveMetaReady = res);
if (IS_EXTENSION) {
const listener = (e: MessageEvent) => {
if (e.data?.type === "vencord:meta") {
({ EXTENSION_BASE_URL, EXTENSION_VERSION } = e.data.meta);
({ EXTENSION_BASE_URL, EXTENSION_VERSION, RENDERER_CSS_URL } = e.data.meta);
window.removeEventListener("message", listener);
resolveMetaReady();
}
};