/* * Vencord, a Discord client mod * Copyright (c) 2023 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import { Settings } from "@api/Settings"; import { getLess, getStylus } from "@utils/dependencies"; import { Logger } from "@utils/Logger"; import { usercssParse } from "."; const UserCSSLogger = new Logger("UserCSS:Compiler", "#d2acf5"); const preprocessors: { [preprocessor: string]: (text: string, vars: Record) => Promise; } = { async default(text: string, vars: Record) { const variables = Object.entries(vars) .map(([name, value]) => `--${name}: ${value}`) .join("; "); return `/* ==Equicord== */\n:root{${variables}}\n/* ==/Equicord== */${text}`; }, async uso(text: string, vars: Record) { for (const [k, v] of Object.entries(vars)) { text = text.replace(new RegExp(`\\/\\*\\[\\[${k}\\]\\]\\*\\/`, "g"), v); } return text; }, async stylus(text: string, vars: Record) { const StylusRenderer = await getStylus(); const variables = Object.entries(vars) .map(([name, value]) => `${name} = ${value}`) .join("\n"); const stylusDoc = `// ==Vencord==\n${variables}\n// ==/Vencord==\n${text}`; return new StylusRenderer(stylusDoc).render(); }, async less(text: string, vars: Record) { const less = await getLess(); const variables = Object.entries(vars) .map(([name, value]) => `@${name}: ${value};`) .join("\n"); const lessDoc = `// ==Vencord==\n${variables}\n// ==/Vencord==\n${text}`; return less.render(lessDoc).then(r => r.css); } }; export async function compileUsercss(fileName: string) { const themeData = await VencordNative.themes.getThemeData(fileName); if (!themeData) return null; // UserCSS preprocessor order look like this: // - use the preprocessor defined // - if variables are set, `uso` // - otherwise, `default` const { vars = {}, preprocessor = Object.keys(vars).length > 0 ? "uso" : "default", id } = await usercssParse(themeData, fileName); const preprocessorFn = preprocessors[preprocessor]; if (!preprocessorFn) { UserCSSLogger.error("File", fileName, "requires preprocessor", preprocessor, "which isn't known to Equicord"); return null; } const varsToPass = { vencord: "true" }; for (const [k, v] of Object.entries(vars)) { varsToPass[k] = Settings.userCssVars[id]?.[k] ?? v.default; switch (v.type) { case "checkbox": { if (["less", "stylus"].includes(preprocessor)) { varsToPass[k] = varsToPass[k] === "1" ? "true" : "false"; } break; } case "range": { varsToPass[k] = `${varsToPass[k]}${v.units ?? "px"}`; break; } case "text": { if (preprocessor === "stylus") { varsToPass[k] = `"${varsToPass[k].replace(/"/g, "\" + '\"' + \"")}"`; } else { varsToPass[k] = `"${varsToPass[k].replace(/\//g, "\\\\").replace(/"/g, '\\"')}"`; } break; } } } try { return await preprocessorFn(themeData, varsToPass); } catch (error) { UserCSSLogger.error("File", fileName, "failed to compile with preprocessor", preprocessor, error); return null; } }