diff --git a/tgui/packages/tgui-panel/chat/renderer.tsx b/tgui/packages/tgui-panel/chat/renderer.tsx
index 0b2a82554f6..b1758b5fb5b 100644
--- a/tgui/packages/tgui-panel/chat/renderer.tsx
+++ b/tgui/packages/tgui-panel/chat/renderer.tsx
@@ -176,6 +176,7 @@ class ChatRenderer {
highlightWholeMessage: boolean;
highlightBlacklist: boolean;
blacklistregex: RegExp;
+ enabled: boolean;
}[]
| null;
databaseBackendEnabled: boolean;
@@ -288,6 +289,7 @@ class ChatRenderer {
const highlightWholeMessage = setting.highlightWholeMessage;
const matchWord = setting.matchWord;
const matchCase = setting.matchCase;
+ const enabled = setting.enabled;
const allowedRegex = /^[a-zа-яё0-9_\-$/^[\s\]\\]+$/gi;
const regexEscapeCharacters = /[!#$%^&*)(+=.<>{}[\]:;'"|~`_\-\\/]/g;
// Reset lastIndex so it does not mess up the next word
@@ -397,6 +399,7 @@ class ChatRenderer {
this.highlightParsers = [];
}
this.highlightParsers.push({
+ enabled,
highlightWords,
highlightRegex,
highlightColor,
@@ -640,31 +643,32 @@ class ChatRenderer {
// Highlight text
if (!message.avoidHighlighting && this.highlightParsers) {
- this.highlightParsers.map((parser) => {
- const ourUser = node.getElementsByClassName('name');
- const isEmote = node.getElementsByClassName('emote');
- if (
- !(
- parser.highlightBlacklist &&
- parser.blacklistregex &&
- ((ourUser.length > 0 &&
- parser.blacklistregex.test(ourUser[0].textContent)) ||
- (isEmote.length > 0 &&
- parser.blacklistregex.test(isEmote[0].textContent)))
- )
- ) {
- const highlighted = highlightNode(
- node,
- parser.highlightRegex,
- parser.highlightWords,
- (text) => createHighlightNode(text, parser.highlightColor),
- );
- if (highlighted && parser.highlightWholeMessage) {
- node.className += ' ChatMessage--highlighted';
+ this.highlightParsers
+ .filter((parser) => parser.enabled)
+ .forEach((parser) => {
+ const ourUser = node.getElementsByClassName('name');
+ const isEmote = node.getElementsByClassName('emote');
+ if (
+ !(
+ parser.highlightBlacklist &&
+ parser.blacklistregex &&
+ ((ourUser.length > 0 &&
+ parser.blacklistregex.test(ourUser[0].textContent)) ||
+ (isEmote.length > 0 &&
+ parser.blacklistregex.test(isEmote[0].textContent)))
+ )
+ ) {
+ const highlighted = highlightNode(
+ node,
+ parser.highlightRegex,
+ parser.highlightWords,
+ (text) => createHighlightNode(text, parser.highlightColor),
+ );
+ if (highlighted && parser.highlightWholeMessage) {
+ node.className += ' ChatMessage--highlighted';
+ }
}
- }
- return undefined;
- });
+ });
}
// Linkify text
const linkifyNodes = node.querySelectorAll('.linkify');
diff --git a/tgui/packages/tgui-panel/settings/SettingTabs/TextHighlightSettings.tsx b/tgui/packages/tgui-panel/settings/SettingTabs/TextHighlightSettings.tsx
index f62ea6f18d5..27c48267958 100644
--- a/tgui/packages/tgui-panel/settings/SettingTabs/TextHighlightSettings.tsx
+++ b/tgui/packages/tgui-panel/settings/SettingTabs/TextHighlightSettings.tsx
@@ -1,4 +1,5 @@
import { useAtomValue } from 'jotai';
+import { useMemo } from 'react';
import {
Box,
Button,
@@ -14,7 +15,7 @@ import { settingsAtom } from '../atoms';
import { MAX_HIGHLIGHT_SETTINGS } from '../constants';
import { useHighlights } from '../use-highlights';
-export const TextHighlightSettings = (props) => {
+export function TextHighlightSettings(props) {
const {
highlights: { highlightSettings },
addHighlight,
@@ -59,9 +60,25 @@ export const TextHighlightSettings = (props) => {
);
-};
+}
-const TextHighlightSetting = (props) => {
+const oneCharacterRegex = /^(\[.*\]|\\.|.)$/;
+
+function extractRegex(highlight: string): string | null {
+ if (
+ highlight.charAt(0) !== '/' ||
+ highlight.charAt(highlight.length - 1) !== '/'
+ ) {
+ return null;
+ }
+ const expr = highlight.substring(1, highlight.length - 1);
+ if (oneCharacterRegex.test(expr)) {
+ return null;
+ }
+ return expr;
+}
+
+function TextHighlightSetting(props) {
const { id, ...rest } = props;
const {
highlights: { highlightSettingById },
@@ -69,6 +86,7 @@ const TextHighlightSetting = (props) => {
removeHighlight,
} = useHighlights();
const {
+ enabled,
highlightColor,
highlightText,
blacklistText,
@@ -77,6 +95,22 @@ const TextHighlightSetting = (props) => {
matchWord,
matchCase,
} = highlightSettingById[id];
+
+ const highlightRegex = useMemo(
+ () => extractRegex(highlightText),
+ [highlightText],
+ );
+
+ const isRegexValid = useMemo(() => {
+ if (!highlightRegex) return true;
+ try {
+ new RegExp(highlightRegex, 'g');
+ return true;
+ } catch {
+ return false;
+ }
+ }, [highlightRegex]);
+
return (
@@ -104,11 +138,26 @@ const TextHighlightSetting = (props) => {
)}
+
+
+ updateHighlight({
+ id,
+ enabled: !enabled,
+ })
+ }
+ >
+ Enabled
+
+
updateHighlight({
@@ -185,6 +234,7 @@ const TextHighlightSetting = (props) => {
height="3em"
value={highlightText}
placeholder="Put words to highlight here. Separate terms with commas, i.e. (term1, term2, term3)"
+ style={{ border: isRegexValid ? '' : '1px solid red' }}
onBlur={(value) =>
updateHighlight({
id,
@@ -208,4 +258,4 @@ const TextHighlightSetting = (props) => {
)}
);
-};
+}
diff --git a/tgui/packages/tgui-panel/settings/atoms.ts b/tgui/packages/tgui-panel/settings/atoms.ts
index c1dca966b96..9e0f3e86b4e 100644
--- a/tgui/packages/tgui-panel/settings/atoms.ts
+++ b/tgui/packages/tgui-panel/settings/atoms.ts
@@ -46,6 +46,7 @@ export const defaultHighlightSetting: HighlightSetting = {
highlightWholeMessage: true,
matchWord: false,
matchCase: false,
+ enabled: true,
};
export const defaultHighlights: HighlightState = {
diff --git a/tgui/packages/tgui-panel/settings/migration.ts b/tgui/packages/tgui-panel/settings/migration.ts
index 0340a7e1621..abc18c54772 100644
--- a/tgui/packages/tgui-panel/settings/migration.ts
+++ b/tgui/packages/tgui-panel/settings/migration.ts
@@ -60,6 +60,17 @@ function migrateHighlights(next: HighlightState): HighlightState {
draft.highlightText ?? defaultHighlightSetting.highlightText;
}
+ // Ensure that all highlights have the "enabled" var,
+ // setting it to true if it doesn't exist.
+ for (const id in draft.highlightSettingById) {
+ if (
+ draft.highlightSettingById[id] &&
+ draft.highlightSettingById[id].enabled === undefined
+ ) {
+ draft.highlightSettingById[id].enabled = true;
+ }
+ }
+
return draft;
}
diff --git a/tgui/packages/tgui-panel/settings/types.ts b/tgui/packages/tgui-panel/settings/types.ts
index 55668843804..da1dbf2b3f9 100644
--- a/tgui/packages/tgui-panel/settings/types.ts
+++ b/tgui/packages/tgui-panel/settings/types.ts
@@ -47,6 +47,7 @@ export type HighlightSetting = {
id: string;
matchCase: boolean;
matchWord: boolean;
+ enabled: boolean;
};
export type HighlightState = {
diff --git a/tgui/packages/tgui/interfaces/ParticleEdit/helpers.ts b/tgui/packages/tgui/interfaces/ParticleEdit/helpers.ts
index 0728f786a8b..198b14e8dcd 100644
--- a/tgui/packages/tgui/interfaces/ParticleEdit/helpers.ts
+++ b/tgui/packages/tgui/interfaces/ParticleEdit/helpers.ts
@@ -40,14 +40,13 @@ export const setGradientSpace = (
space: number,
) => {
let found = false;
- gradient?.map((entry) => {
+ gradient?.forEach((entry) => {
if (typeof entry === 'object') {
if (Object.keys(entry)[0] === 'space') {
entry.space = space;
found = true;
}
}
- return undefined;
});
if (!found) {
gradient.push({ space: space });
diff --git a/tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/VoreContentsPanel.tsx b/tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/VoreContentsPanel.tsx
index 1fbaa714a41..c22c6ce583c 100644
--- a/tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/VoreContentsPanel.tsx
+++ b/tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/VoreContentsPanel.tsx
@@ -56,18 +56,8 @@ export const VoreContentsPanel = (props: {
}, [contents]);
function bellyValueToName(value: string) {
- const bellyName = bellyDropdownNames
- ?.map((entry) => {
- if (entry.value === value) {
- return entry.displayText;
- }
- return undefined;
- })
- .filter((value) => value !== undefined);
- if (Array.isArray(bellyName) && bellyName.length) {
- return bellyName[0];
- }
- return '';
+ const entry = bellyDropdownNames?.find((entry) => entry.value === value);
+ return entry ? entry.displayText : '';
}
const contentSearch = createSearch(