add plugin FixHardcodedColors

This commit is contained in:
F53 2024-07-31 21:38:52 -06:00
parent 67dfaa95ba
commit 6ae30fa2fa
2 changed files with 115 additions and 0 deletions

View file

@ -0,0 +1,22 @@
Discord often hardcodes colors despite having css variables for all it's colors.
For example, `--primary-160` is `#ebedef`.
But in the code, they have hardcoded the color hex instead of using the variable
```css
.defaultLightModeCustomGradient_e77fa3 {
background: linear-gradient(rgba(0,0,0,0) 20%, #ebedef 100%);
}
```
This causes issues for theme devs who want to make stuff by directly modifying color variables as they need to manually fix all these problems.
This is very prevalent when using ClientTheme and looking at "channels and roles"
![Discord_tn6oWjipFv](https://github.com/Vendicated/Vencord/assets/37855219/e74e41af-b277-4b28-83be-f87807bad16d)
This plugin addresses this issue by generating css to make the problematic code use color variables instead, for example:
```css
.defaultLightModeCustomGradient_e77fa3 {
background: linear-gradient(rgba(0,0,0,0) 20%, var(--primary-160) 100%);
}
```

View file

@ -0,0 +1,93 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import definePlugin, { StartAt } from "@utils/types";
import { createStyleSheet, getStyles, newStyleListener } from "plugins/clientTheme/cssUtil";
export default definePlugin({
name: "Fix hardcoded colors",
description: "replace hardcoded colors with color variables",
authors: [Devs.F53],
startAt: StartAt.DOMContentLoaded,
async start() {
const styles = await getStyles();
const colorVariables = getColorVariables(styles);
const fixSheet = createStyleSheet("hardcodedColorFixes", generateFixes(colorVariables, styles));
newStyleListener(styles => fixSheet.innerText += generateFixes(colorVariables, styles));
},
stop() {
document.getElementById("hardcodedColorFixes")?.remove();
}
});
function generateFixes(colorVariables: ColorVariable[], styles: string) {
const stylesToFix = getStylesWithColors(styles);
let out = "";
for (const style of stylesToFix)
out += generateFix(colorVariables, style);
return out;
}
function generateFix(colorVariables: ColorVariable[], problematicStyle: string) {
const selector = /^[^{]*/.exec(problematicStyle)?.[0];
const rules = Array.from(problematicStyle.matchAll(/(?:{|;)([a-z-]+):([^;}]+)/g), match => ({ property: match[1], value: match[2] }));
if (!selector) return "";
const fixes: string[] = [];
for (const rule of rules) {
let fixedValue = rule.value;
for (const match of Array.from(rule.value.matchAll(/#[a-f\d]{6}|rgb\(\d+,\d+\d+\)/g))) {
const rgb = toRGB(match[0]);
for (const color of colorVariables) {
const distance = rgb.reduce((totalDistance, b, i) => totalDistance + Math.abs(b - color.rgb[i]), 0);
if (distance > 5) continue;
fixedValue = fixedValue.replaceAll(match[0], ` var(${color.variable}) `);
break; // already found variable to replace it, don't keep looking
}
}
fixedValue.replaceAll(" ", "");
if (fixedValue !== rule.value)
fixes.push(`${rule.property}:${fixedValue}`);
}
if (fixes.length === 0) return "";
return `${selector}{${fixes.join(";")}}`;
}
const cssWithColorRegex = /(?:^|})[^{}]+?{[^}]*?(?:#[a-f\d]{6}|rgb\(\d+,\d+,\d+\))[^}]*?}/g;
// gets array of styles that hardcode color
function getStylesWithColors(styles: string) {
return Array.from(styles.matchAll(cssWithColorRegex), match => {
if (match[0][0] === "}") return match[0].slice(1);
return match[0];
});
}
const variableRegex = /(--[a-z-\d]*?)-hsl:(\d+).*?(\d+\.?\d*)%.*?(\d+\.?\d*)%/g;
interface ColorVariable { variable: string, rgb: [number, number, number]; }
function getColorVariables(styles: string): ColorVariable[] {
return Array.from(styles.matchAll(variableRegex), match => {
const variable = match[1];
const [h, s, l] = match.slice(2);
return { variable, rgb: toRGB(`hsl(${h},${s}%,${l}%)`) };
}).filter(color => // ignore solid white/black colors because they are weird
color.rgb.some(b => b !== 255) && color.rgb.some(b => b !== 0)
).toSorted(a => // prefer --primary colors over anything else
a.variable.startsWith("--primary") ? -1 : 0
);
}
function toRGB(color: string) {
// https://stackoverflow.com/a/74662179/8133370
const { style } = new Option();
style.color = color; // for some reason this is immediately translated into "rgb(x, x, x)", no matter the input
// turn into array [r: number, g: number, b: number]
return Array.from(style.color.matchAll(/\b\d+\b/g)).flatMap(Number) as [number, number, number];
}