import { LocalizeText } from './LocalizeText'; const allowedColours: Map = new Map(); allowedColours.set('r', 'red'); allowedColours.set('b', 'blue'); allowedColours.set('g', 'green'); allowedColours.set('y', 'yellow'); allowedColours.set('w', 'white'); allowedColours.set('o', 'orange'); allowedColours.set('c', 'cyan'); allowedColours.set('br', 'brown'); allowedColours.set('pr', 'purple'); allowedColours.set('pk', 'pink'); allowedColours.set('red', 'red'); allowedColours.set('blue', 'blue'); allowedColours.set('green', 'green'); allowedColours.set('yellow', 'yellow'); allowedColours.set('white', 'white'); allowedColours.set('orange', 'orange'); allowedColours.set('cyan', 'cyan'); allowedColours.set('brown', 'brown'); allowedColours.set('purple', 'purple'); allowedColours.set('pink', 'pink'); const encodeHTML = (str: string) => { return str.replace(/([\u00A0-\u9999<>&])(.|$)/g, (full, char, next) => { if(char !== '&' || next !== '#') { if(/[\u00A0-\u9999<>&]/.test(next)) next = '&#' + next.charCodeAt(0) + ';'; return '&#' + char.charCodeAt(0) + ';' + next; } return full; }); }; const formatTag = (content: string, tag: string, replacement: (value: string) => string) => { const pattern = new RegExp(`\\[${ tag }\\]([\\s\\S]*?)\\[\\/${ tag }\\]`, 'gi'); let previous = ''; let next = content; let guard = 0; while((previous !== next) && (guard < 20)) { previous = next; next = next.replace(pattern, (match, value) => replacement(value)); guard++; } return next; }; const applyWiredTextMarkup = (content: string) => { const colorStyles: Record = { green: '#008000', cyan: '#008b8b', red: '#d60000', blue: '#005dff', purple: '#7d31b8' }; let result = content; result = formatTag(result, 'b', value => `${ value }`); result = formatTag(result, 'i', value => `${ value }`); result = formatTag(result, 'u', value => `${ value }`); Object.entries(colorStyles).forEach(([ tag, color ]) => { result = formatTag(result, tag, value => `${ value }`); }); 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 = /]{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 `${ inner }`; }); guard++; } return next; }; export const RoomChatFormatter = (content: string) => { let result = ''; content = encodeHTML(content); content = applyFontMarkup(content); content = applyWiredTextMarkup(content); //content = (joypixels.shortnameToUnicode(content) as string) if(content.startsWith('@') && content.indexOf('@', 1) > -1) { let match = null; while((match = /@[a-zA-Z]+@/g.exec(content)) !== null) { const colorTag = match[0].toString(); const colorName = colorTag.substr(1, colorTag.length - 2); const text = content.replace(colorTag, ''); if(!allowedColours.has(colorName)) { result = text; } else { const color = allowedColours.get(colorName); result = '' + text + ''; } break; } } else { result = content; } return result.replace(/\r\n|\r|\n/g, '
'); };