mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
🆙 Fix font colors in chat
This commit is contained in:
@@ -80,11 +80,97 @@ const applyWiredTextMarkup = (content: string) =>
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FONT_NAMED_COLORS = new Set([
|
||||||
|
'red', 'green', 'blue', 'yellow', 'white', 'black',
|
||||||
|
'orange', 'cyan', 'brown', 'purple', 'pink', 'magenta',
|
||||||
|
'violet', 'gray', 'grey', 'lime', 'teal', 'gold',
|
||||||
|
'silver', 'navy', 'maroon', 'olive', 'indigo'
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const sanitizeFontColor = (raw: string | null | undefined): string | null =>
|
||||||
|
{
|
||||||
|
if(!raw) return null;
|
||||||
|
if(raw.length > 20) return null;
|
||||||
|
|
||||||
|
const value = raw.trim().toLowerCase();
|
||||||
|
|
||||||
|
if(/^#([0-9a-f]{3}|[0-9a-f]{6})$/.test(value)) return value;
|
||||||
|
if(FONT_NAMED_COLORS.has(value)) return value;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FontSegment = { color: string | null; text: string };
|
||||||
|
|
||||||
|
const FONT_COLOR_ATTR = /color\s*=\s*(?:"([^"]{1,32})"|'([^']{1,32})'|([^\s"'>]{1,32}))/i;
|
||||||
|
|
||||||
|
export const parseFontSegments = (input: string): FontSegment[] =>
|
||||||
|
{
|
||||||
|
if(!input) return [];
|
||||||
|
|
||||||
|
const pattern = /<font\b([^>]{0,200}?)>([\s\S]{0,200}?)<\/font>/gi;
|
||||||
|
const segments: FontSegment[] = [];
|
||||||
|
|
||||||
|
let lastIndex = 0;
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
|
while((match = pattern.exec(input)) !== null)
|
||||||
|
{
|
||||||
|
if(match.index > lastIndex)
|
||||||
|
{
|
||||||
|
segments.push({ color: null, text: input.slice(lastIndex, match.index) });
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorMatch = FONT_COLOR_ATTR.exec(match[1] || '');
|
||||||
|
const rawColor = colorMatch ? (colorMatch[1] || colorMatch[2] || colorMatch[3]) : null;
|
||||||
|
const color = sanitizeFontColor(rawColor);
|
||||||
|
|
||||||
|
segments.push({ color, text: match[2] });
|
||||||
|
lastIndex = pattern.lastIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lastIndex < input.length)
|
||||||
|
{
|
||||||
|
segments.push({ color: null, text: input.slice(lastIndex) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments;
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyFontMarkup = (content: string) =>
|
||||||
|
{
|
||||||
|
const fontPattern = /<font\b([^&]{0,200}?)>([\s\S]{0,4000}?)<\/font>/gi;
|
||||||
|
const colorAttr = /color\s*=\s*(?:"([^"]{1,32})"|'([^']{1,32})'|([^\s"'>]{1,32}))/i;
|
||||||
|
|
||||||
|
let previous = '';
|
||||||
|
let next = content;
|
||||||
|
let guard = 0;
|
||||||
|
|
||||||
|
while((previous !== next) && (guard < 20))
|
||||||
|
{
|
||||||
|
previous = next;
|
||||||
|
next = next.replace(fontPattern, (_match, attrs: string, inner: string) =>
|
||||||
|
{
|
||||||
|
const colorMatch = colorAttr.exec(attrs || '');
|
||||||
|
const rawColor = colorMatch ? (colorMatch[1] || colorMatch[2] || colorMatch[3]) : null;
|
||||||
|
const color = sanitizeFontColor(rawColor);
|
||||||
|
|
||||||
|
if(!color) return inner;
|
||||||
|
|
||||||
|
return `<span style="color:${ color }">${ inner }</span>`;
|
||||||
|
});
|
||||||
|
guard++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
export const RoomChatFormatter = (content: string) =>
|
export const RoomChatFormatter = (content: string) =>
|
||||||
{
|
{
|
||||||
let result = '';
|
let result = '';
|
||||||
|
|
||||||
content = encodeHTML(content);
|
content = encodeHTML(content);
|
||||||
|
content = applyFontMarkup(content);
|
||||||
content = applyWiredTextMarkup(content);
|
content = applyWiredTextMarkup(content);
|
||||||
//content = (joypixels.shortnameToUnicode(content) as string)
|
//content = (joypixels.shortnameToUnicode(content) as string)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,23 @@
|
|||||||
import { FC, useMemo } from 'react';
|
import { FC, Fragment, ReactNode, useMemo } from 'react';
|
||||||
import { GetNickIconUrl } from '../assets/images/user_custom/nick_icons';
|
import { GetNickIconUrl } from '../assets/images/user_custom/nick_icons';
|
||||||
import { PREFIX_EFFECT_KEYFRAMES, getPrefixEffectStyle, getPrefixFontStyle, parsePrefixColors } from '../api';
|
import { PREFIX_EFFECT_KEYFRAMES, getPrefixEffectStyle, getPrefixFontStyle, parseFontSegments, parsePrefixColors } from '../api';
|
||||||
|
|
||||||
|
const renderInlineFontMarkup = (text: string): ReactNode =>
|
||||||
|
{
|
||||||
|
if(!text) return text;
|
||||||
|
if(text.indexOf('<font') === -1) return text;
|
||||||
|
|
||||||
|
const segments = parseFontSegments(text);
|
||||||
|
|
||||||
|
if(!segments.length) return text;
|
||||||
|
|
||||||
|
return segments.map((segment, index) =>
|
||||||
|
{
|
||||||
|
if(segment.color) return <span key={ index } style={ { color: segment.color } }>{ segment.text }</span>;
|
||||||
|
|
||||||
|
return <Fragment key={ index }>{ segment.text }</Fragment>;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
interface UserIdentityViewProps
|
interface UserIdentityViewProps
|
||||||
{
|
{
|
||||||
@@ -87,7 +104,7 @@ export const UserIdentityView: FC<UserIdentityViewProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
case 'name':
|
case 'name':
|
||||||
return <span key="identity-name" className={ `${ nameClassName } whitespace-nowrap` }>{ username }{ showColon ? ':' : '' }{ showColon ? ' ' : '' }</span>;
|
return <span key="identity-name" className={ `${ nameClassName } whitespace-nowrap` }>{ renderInlineFontMarkup(username) }{ showColon ? ':' : '' }{ showColon ? ' ' : '' }</span>;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user