Introduce MiniMessage rendering system for structured chat formatting and interaction
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user