58 lines
2.2 KiB
TypeScript
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, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
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;
|
|
}
|