Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Plugin] Message Colors #2521

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c8602ef
Fix reporter breaking because of ConsoleShortcuts
Nuckyz May 29, 2024
7f1382b
messageColors: new plugin
henmalib May 29, 2024
0ed37f5
messageColors: added README
henmalib May 29, 2024
4493c11
Merge branch 'dev' into plugin_message_colors
henmalib May 29, 2024
ec2a133
messageColors: allow to render foreground color
henmalib May 30, 2024
0e2b17e
messageColors: added color picker
henmalib May 30, 2024
708426c
messageColors: add option to not require # at hex
henmalib Jun 2, 2024
5577c11
messageColors: fixed color picker
henmalib Jun 16, 2024
62f829b
Discard changes to scripts/generateReport.ts
Vendicated Jun 17, 2024
c861fd9
Merge branch 'dev' into plugin_message_colors
Vendicated Jun 17, 2024
b39bff9
messageColors: requested changes
henmalib Jun 17, 2024
98fd3ed
Merge branch 'Vendicated:main' into plugin_message_colors
henmalib Jun 21, 2024
f98031c
fixed patch, changed invert color
henmalib Jun 21, 2024
484c124
messageColors: removed 'floating #', hsl now supports '°'
henmalib Jun 21, 2024
a7ca732
messageColors: use md rules instead of dynamic react!
henmalib Jun 27, 2024
1d377ae
messageColors: nuked NONE render
henmalib Jun 27, 2024
dcc625e
Merge branch 'dev' into plugin_message_colors
henmalib Jun 27, 2024
eb2b006
messageColors: yeah, right, I don't need it anymore
henmalib Jun 27, 2024
763e27b
messageColors: requiredFirstCharacters supports more than 1char!
henmalib Jun 27, 2024
9c103d4
messageColors: nuked color picker, update README
henmalib Jun 27, 2024
838732e
messageColors: grouped patches
henmalib Jun 28, 2024
8805dab
messageColors: silly
henmalib Jun 28, 2024
4307e1d
messageColors: support different cases like RgB hSl
henmalib Jun 28, 2024
54530b9
Merge branch 'Vendicated:main' into plugin_message_colors
henmalib Jun 28, 2024
c7998d1
Merge branch 'dev' into plugin_message_colors
henmalib Jul 7, 2024
a367429
messageColors: rgba support/space instead of comma
henmalib Jul 12, 2024
31eaf5e
Merge branch 'Vendicated:main' into plugin_message_colors
henmalib Aug 2, 2024
465cb3c
messageColors: setting to change block position
henmalib Aug 12, 2024
9f2f3f9
better color ( background ), ErrorBoundary, better color border for bg
henmalib Aug 18, 2024
3ded6eb
use import type instead
henmalib Aug 18, 2024
4b26649
MessageColors: option to disable 3char hex
henmalib Aug 19, 2024
9e73f3d
MessageColors: option rename
henmalib Aug 19, 2024
4ae583a
Dearrow: Add option to not dearrow by default (#2818)
electricsteve Sep 4, 2024
7768927
Merge branch 'dev' into plugin_message_colors
henmalib Sep 4, 2024
38f630f
Discard changes to src/plugins/dearrow/index.tsx
henmalib Sep 7, 2024
076fc4d
Merge branch 'main' into plugin_message_colors
henmalib Sep 12, 2024
c9f4b86
Merge branch 'main' into plugin_message_colors
henmalib Sep 22, 2024
ce89bfa
Merge branch 'main' into plugin_message_colors
henmalib Sep 23, 2024
ce75e69
Merge branch 'main' into plugin_message_colors
henmalib Sep 26, 2024
f79214e
fix: text regex is not beign affected
henmalib Sep 27, 2024
2a2f4df
Merge branch 'Vendicated:main' into plugin_message_colors
henmalib Oct 17, 2024
1bc1233
MessageColors(fix): hyperlinks like [#123](url) are not rendered corr…
henmalib Oct 31, 2024
5e812f3
Merge branch 'Vendicated:main' into plugin_message_colors
henmalib Jan 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/plugins/messageColors/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### MessageColors

Displays color codes like #cba6f7 or rgb(255,0,0) inside of messages

#### Block Display

![](https://github.com/Vendicated/Vencord/assets/68553709/c72c82a7-cec0-471f-a52a-2f28cee6ec00)

#### Colored Text

![](https://github.com/Vendicated/Vencord/assets/68553709/f1d545a5-aa54-42af-bdcf-8e8b30e9b91d)

#### Colored Background

![](https://github.com/Vendicated/Vencord/assets/68553709/032b0d50-bbcd-4572-a08a-b69593ed3322)
95 changes: 95 additions & 0 deletions src/plugins/messageColors/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

import { definePluginSettings } from "@api/Settings";
import { OptionType } from "@utils/types";

export const enum RenderType {
BLOCK,
FOREGROUND,
BACKGROUND,
}

export const enum BlockDisplayType {
LEFT,
RIGHT,
BOTH
}

export const settings = definePluginSettings({
renderType: {
type: OptionType.SELECT,
description: "How to render colors",
options: [
{
label: "Text color",
value: RenderType.FOREGROUND,
default: true,
},
{
label: "Block nearby",
value: RenderType.BLOCK,
},
{
label: "Background color",
value: RenderType.BACKGROUND
},
]
},
enableShortHexCodes: {
type: OptionType.BOOLEAN,
description: "Enable 3 char hex-code like #39f",
default: true,
// Regex are created on the start, so without restart nothing would change
restartNeeded: true
henmalib marked this conversation as resolved.
Show resolved Hide resolved
},
blockView: {
type: OptionType.SELECT,
disabled: () => settings.store.renderType !== RenderType.BLOCK,
description: "Where to display colored block",
options: [
{
label: "Right side",
value: BlockDisplayType.RIGHT,
default: true
},
{
label: "Left side",
value: BlockDisplayType.LEFT
},
{
label: "Both sides",
value: BlockDisplayType.BOTH
}
]
}
});

export const enum ColorType {
RGB,
RGBA,
HEX,
HSL
}

// It's sooo hard to read regex without this, it makes it at least somewhat bearable
export const replaceRegexp = (reg: string) => {
const n = new RegExp(reg
// \c - 'comma'
// \v - 'value'
// \f - 'float'
.replaceAll("\\f", "[+-]?([0-9]*[.])?[0-9]+")
.replaceAll("\\c", "(?:,|\\s)")
.replaceAll("\\v", "\\s*?\\d+?\\s*?"), "g");

return n;
};

export const regex = [
{ reg: /rgb\(\v\c\v\c\v\)/g, type: ColorType.RGB },
{ reg: /rgba\(\v\c\v\c\v(\c|\/?)\s*\f\)/g, type: ColorType.RGBA },
{ reg: /hsl\(\v°?\c\s*?\d+%?\s*?\c\s*?\d+%?\s*?\)/g, type: ColorType.HSL },
].map(v => { v.reg = replaceRegexp(v.reg.source); return v; });
229 changes: 229 additions & 0 deletions src/plugins/messageColors/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

import "./styles.css";

import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { StartAt } from "@utils/types";
import { React } from "@webpack/common";
import type { ReactElement } from "react";

import { BlockDisplayType, ColorType, regex, RenderType, replaceRegexp, settings } from "./constants";


interface ParsedColorInfo {
type: "color";
color: string;
colorType: ColorType;
text: string;
}

const requiredFirstCharacters = ["r", "h", "#"].flatMap(v => [v, v.toUpperCase()]);

export default definePlugin({
authors: [Devs.hen],
name: "MessageColors",
description: "Displays color codes like #FF0042 inside of messages",
settings,
patches: [
// Create a new markdown rule, so it parses just like any other features
// Like bolding, spoilers, mentions, etc
{
find: "roleMention:{order:",
group: true,
replacement: {
match: /roleMention:\{order:(\i\.\i\.order)/,
replace: "color:$self.getColor($1),$&"
}
},
// Changes text md rule regex, so it stops right before hsl( | rgb(
// Without it discord will try to pass a string without those to color rule
{
find: ".defaultRules.text,match:",
group: true,
replacement: {
// $)/)
match: /\$\)\//,
// hsl(|rgb(|$&
replace: requiredFirstCharacters.join("|") + "|$&"
}
},
// Fix the issue with [#123](https://example.com) rendered as plain text
{
find: "parseInlineCodeChildContent:",
replacement: {
match: /parseInlineCodeChildContent:/,
replace: "isInsideOfLink:true,$&"
}
},
// Discord just requires it to be here
// Or it explodes (bad)
{
find: "Unknown markdown rule:",
group: true,
replacement: {
match: /roleMention:{type:/,
replace: "color:{type:\"inlineObject\"},$&",
}
},
],
start() {
const amount = settings.store.enableShortHexCodes ? "{1,2}" : "{2}";
regex.push({
reg: new RegExp("#(?:[0-9a-fA-F]{3})" + amount, "g"),
type: ColorType.HEX
});
},
// Needed to load all regex before patching
startAt: StartAt.Init,
getColor(order: number) {
const source = regex.map(r => r.reg.source).join("|");
const matchAllRegExp = new RegExp(`^(${source})`, "i");

return {
order,
// Don't even try to match if the message chunk doesn't start with...
requiredFirstCharacters,
// Match -> Parse -> React
// Result of previous action is dropped as a first argument of the next one
match(content: string) {
return matchAllRegExp.exec(content);
},
parse(matchedContent: RegExpExecArray, _, parseProps: Record<string, any>):
ParsedColorInfo | ({ type: "text", content: string; }) {
// This check makes sure that it doesn't try to parse color
// When typing/editing message
//
// Discord doesn't know how to deal with color and crashes
if (!parseProps.messageId || parseProps.isInsideOfLink) return {
type: "text",
content: matchedContent[0]
};

const content = matchedContent[0];
try {
const type = getColorType(content);

return {
type: "color",
colorType: type,
color: parseColor(content, type),
text: content
};
} catch (e) {
console.error(e);
return {
type: "text",
content: matchedContent[0]
};
}
},
react: ErrorBoundary.wrap(({ text, colorType, color }: ParsedColorInfo) => {
if (settings.store.renderType === RenderType.FOREGROUND) {
return <span style={{ color: color }}>{text}</span>;
}
const styles = {
"--color": color
} as React.CSSProperties;

if (settings.store.renderType === RenderType.BACKGROUND) {
const isDark = isColorDark(color, colorType);
const className = `vc-color-bg ${!isDark ? "vc-color-bg-invert" : ""}`;
return <span className={className} style={styles}>{text}</span>;
}

// Only block display left
const margin = "2px";

switch (settings.store.blockView) {
case BlockDisplayType.LEFT:
styles.marginRight = margin;
return <><span className="vc-color-block" style={styles} />{text}</>;

case BlockDisplayType.RIGHT:
styles.marginLeft = margin;
return <>{text}<span className="vc-color-block" style={styles} /></>;

case BlockDisplayType.BOTH:
styles.marginLeft = margin;
styles.marginRight = margin;
return <>
<span className="vc-color-block" style={styles} />
{text}
<span className="vc-color-block" style={styles} />
</>;
}
}, {
fallback: data => {
const child = data.children as ReactElement<any>;
return <>{child.props?.text}</>;
}
})
};
}
});

// https://en.wikipedia.org/wiki/Relative_luminance
const calcRGBLightness = (r: number, g: number, b: number) => {
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const isColorDark = (color: string, type: ColorType): boolean => {
const border = 115;
switch (type) {
case ColorType.RGBA:
case ColorType.RGB: {
const match = color.match(/\d+/g)!;
const lightness = calcRGBLightness(+match[0], +match[1], +match[2]);
return lightness < border;
}
case ColorType.HEX: {
color = color.substring(1);
if (color.length === 3) {
color = color.split("").flatMap(v => [v, v]).join("");
}

const rgb = parseInt(color, 16);
const r = (rgb >> 16) & 0xff;
const g = (rgb >> 8) & 0xff;
const b = (rgb >> 0) & 0xff;

const lightness = calcRGBLightness(r, g, b);
return lightness < border;
}
case ColorType.HSL: {
const match = color.match(/\d+/g)!;
const lightness = +match[2];
return lightness < (border / 255 * 100);
}
}
};

const getColorType = (color: string): ColorType => {
color = color.toLowerCase().trim();
if (color.startsWith("#")) return ColorType.HEX;
if (color.startsWith("hsl")) return ColorType.HSL;
if (color.startsWith("rgba")) return ColorType.RGBA;
if (color.startsWith("rgb")) return ColorType.RGB;

throw new Error(`Can't resolve color type of ${color}`);
};

function parseColor(str: string, type: ColorType): string {
str = str.toLowerCase().trim().replaceAll(/(\s|,)+/g, " ");
switch (type) {
case ColorType.RGB:
return str;
case ColorType.RGBA:
if (!str.includes("/"))
return str.replaceAll(replaceRegexp(/\f(?=\s*?\))/.source), "/$&");
return str;
case ColorType.HEX:
return str[0] === "#" ? str : `#${str}`;
case ColorType.HSL:
return str.replace("°", "");
}
}
21 changes: 21 additions & 0 deletions src/plugins/messageColors/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.vc-color-block {
aspect-ratio: 1/1;
background: var(--color);
height: 1rem;
vertical-align: middle;
margin-bottom: 0.2rem;
border-radius: 4px;
display: inline-block;
user-select: none;
box-sizing: border-box;
}

.vc-color-bg {
background: var(--color);
}

/* Light color in dark theme */
.theme-dark .vc-color-bg.vc-color-bg-invert,
.theme-light .vc-color-bg:not(.vc-color-bg-invert) {
color: var(--background-secondary);
}
4 changes: 4 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "verticalsync.",
id: 1207087393929171095n
},
hen: {
id: 279266228151779329n,
name: "Hen"
},
nekohaxx: {
name: "nekohaxx",
id: 1176270221628153886n
Expand Down
Loading