Files
AltitudeWeb/frontend/src/app/pages/altitude/chat/mini-message/mini-message-html.util.ts
T

58 lines
2.2 KiB
TypeScript

import { MiniMessageComponent, ResolvedStyle } from './mini-message.types';
import { hoverEventToTitle, mergeStyle, ownText, styleToNgStyle } from './mini-message.util';
/**
* Escapes text for safe insertion into HTML. Always used on raw text content —
* never skip this when building the string yourself.
*/
function escapeHtml(input: string): string {
return input
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function ngStyleToCss(style: Record<string, string>): string {
return Object.entries(style)
.map(([key, value]) => `${key}:${value}`)
.join(';');
}
/**
* Renders a component-JSON tree to an HTML string, e.g. for binding via
* [innerHTML]="miniMessageToHtml(json) | ..." with Angular's DomSanitizer, or for use
* outside Angular entirely. Prefer the <mini-message> component when you need click/hover
* interactivity or the obfuscated animation — this string form is static (obfuscated text
* renders as a fixed placeholder rather than an animated one).
*/
export function miniMessageToHtml(
node: MiniMessageComponent,
parentStyle?: ResolvedStyle
): string {
const style = mergeStyle(parentStyle, node);
const css = ngStyleToCss(styleToNgStyle(style));
const text = escapeHtml(ownText(node));
const title = hoverEventToTitle(node.hoverEvent);
const titleAttr = title ? ` title="${escapeHtml(title)}"` : '';
const insertionAttr = node.insertion ? ` data-insertion="${escapeHtml(node.insertion)}"` : '';
let ownHtml: string;
if (node.clickEvent?.action === 'open_url') {
ownHtml = `<a href="${escapeHtml(
node.clickEvent.value
)}" target="_blank" rel="noopener noreferrer" class="mm-node mm-link" style="${css}"${titleAttr}${insertionAttr}>${text}</a>`;
} else {
const clickAttrs = node.clickEvent
? ` class="mm-node mm-clickable" data-click-action="${node.clickEvent.action}" data-click-value="${escapeHtml(
node.clickEvent.value
)}"`
: ' class="mm-node"';
ownHtml = `<span${clickAttrs} style="${css}"${titleAttr}${insertionAttr}>${text}</span>`;
}
const childrenHtml = (node.extra ?? []).map((child) => miniMessageToHtml(child, style)).join('');
return ownHtml + childrenHtml;
}