From a8c52486e60eaf1198cdaa07c822245572dc7a67 Mon Sep 17 00:00:00 2001 From: Jake <134332787+justjxke@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:58:45 +0100 Subject: [PATCH] fix tone indicator regex (#436) * add serialization for settings and a myraid of other fixes rewrites. * test dummy settings * fix issue with commits not being logged, rewrite some elements * remove dummy setting * fix tone indicators * support for custom prefix, better regex * even MORE custom prefix support rahhh --------- Co-authored-by: Jake --- .../toneIndicators/ToneIndicator.tsx | 4 +- src/equicordplugins/toneIndicators/index.tsx | 51 +++++++++++++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/equicordplugins/toneIndicators/ToneIndicator.tsx b/src/equicordplugins/toneIndicators/ToneIndicator.tsx index 99d5a4ed0..631b5736e 100644 --- a/src/equicordplugins/toneIndicators/ToneIndicator.tsx +++ b/src/equicordplugins/toneIndicators/ToneIndicator.tsx @@ -8,11 +8,13 @@ import { Tooltip } from "@webpack/common"; import type { ReactElement } from "react"; export interface ToneIndicatorProps { + prefix: string; indicator: string; desc: string; } export default function ToneIndicator({ + prefix, indicator, desc, }: ToneIndicatorProps): ReactElement { @@ -26,7 +28,7 @@ export default function ToneIndicator({ userSelect: "text", }} > - /{indicator} + {prefix}{indicator} )} diff --git a/src/equicordplugins/toneIndicators/index.tsx b/src/equicordplugins/toneIndicators/index.tsx index 1e4bc136b..19bcda6cd 100644 --- a/src/equicordplugins/toneIndicators/index.tsx +++ b/src/equicordplugins/toneIndicators/index.tsx @@ -14,6 +14,11 @@ import indicatorsDefault from "./indicators"; import ToneIndicator from "./ToneIndicator"; const settings = definePluginSettings({ + prefix: { + type: OptionType.STRING, + description: "Prefix character(s) for tone indicators.", + default: "/", + }, customIndicators: { type: OptionType.STRING, description: "Custom tone indicators (format: jk=Joking; srs=Serious)", @@ -53,11 +58,42 @@ function getIndicator(text: string): string | null { ); } +function buildIndicatorRegex(): RegExp { + const customIndicators = getCustomIndicators(); + const allIndicators = new Set(); + + indicatorsDefault.forEach((_, key) => { + allIndicators.add(key.replace(/^_/, "")); // remove underscore prefix for aliases + }); + Object.keys(customIndicators).forEach(key => { + allIndicators.add(key.replace(/^_/, "")); // remove underscore prefix for aliases + }); + + // escape special regex characters and sort by length (longest first) + const escaped = Array.from(allIndicators) + .map(ind => ind.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) + .sort((a, b) => b.length - a.length); // longest first to avoid partial matches (should fix some edge cases) + + const prefix = settings.store.prefix || "/"; + let escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + + // if prefix is a markdown character, also match escaped version + const isMarkdown = /[*_~`|]/.test(prefix); + if (isMarkdown) { + escapedPrefix = `(?:\\\\${escapedPrefix}|${escapedPrefix})`; + } + + // exclude forward slash from punctuation to prevent sed syntax conflicts (s/find/replace) + const pattern = `(?:^|\\s)${escapedPrefix}(${escaped.join("|")})(?=\\s|$|[^\\s\\w/])`; + return new RegExp(pattern, "giu"); // 'i' = case-insensitive, 'u' = unicode +} + function splitTextWithIndicators(text: string): ReactNode[] { const nodes: ReactNode[] = []; let lastIndex = 0; let count = 0; - const regex = /(?:^|\s)\/([^/\s]+)(?=\s|$|\p{P})/giu; + const regex = buildIndicatorRegex(); + const prefix = settings.store.prefix || "/"; let match: RegExpExecArray | null; while ((match = regex.exec(text)) && count < settings.store.maxIndicators) { @@ -75,6 +111,7 @@ function splitTextWithIndicators(text: string): ReactNode[] { nodes.push( , @@ -82,7 +119,6 @@ function splitTextWithIndicators(text: string): ReactNode[] { count++; } - // always advance lastIndex to avoid skipping consecutive indicators lastIndex = matchEnd; } @@ -96,7 +132,16 @@ function patchChildrenTree(children: any): any { if (node == null) return node; if (typeof node === "string") { - if (!/\/[a-z]+/i.test(node)) return node; + const prefix = settings.store.prefix || "/"; + let escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + + // if prefix is markdown character, also check for escaped version + const isMarkdown = /[*_~`|]/.test(prefix); + if (isMarkdown) { + escapedPrefix = `(?:\\\\${escapedPrefix}|${escapedPrefix})`; + } + + if (!new RegExp(`${escapedPrefix}[\\p{L}_]+`, "iu").test(node)) return node; const parts = splitTextWithIndicators(node); return parts.length === 1 ? parts[0] : parts; }