/** * Local copies of app.js DOM helpers used by the Timeline island. * Icons are a closed allowlist (app.js:7-77); innerHTML is the original * SVG injection, not a template compiler. */ const ICONS: Record = { x: '', check: '', sliders: '', refresh: '', stop: '', lock: '', "message-square": '', terminal: '', users: '', }; export function $(sel: string): HTMLElement | null { if (typeof document === "undefined") return null; return document.querySelector(sel); } export function el( tag: string, className?: string | null, text?: string | number | null, ): HTMLElement { const e = document.createElement(tag); if (className) e.className = className; if (text != null) e.textContent = String(text); return e; } export function icon(name: string, size?: number, cls?: string): string { return ``; } export function iconEl(name: string, size?: number, cls?: string): Node { const s = el("span", "ic"); s.innerHTML = icon(name, size, cls); return s.firstChild || s; } export function ghostIconBtn(name: string, title?: string): HTMLButtonElement { const b = el("button", "icon-ghost") as HTMLButtonElement; b.innerHTML = icon(name, 16); if (title) b.title = title; return b; } export function svgElement( name: string, attrs: Record | null = null, ): SVGElement { const node = document.createElementNS("http://www.w3.org/2000/svg", name); Object.entries(attrs || {}).forEach(([key, value]) => node.setAttribute(key, String(value)), ); return node; } export function bytes(b: number): string { b = b || 0; if (b < 1024) return b + " B"; if (b < 1048576) return (b / 1024).toFixed(1) + " KB"; return (b / 1048576).toFixed(1) + " MB"; }