export function h(tag, props = {}, ...children) { const el = document.createElement(tag); const { class: className, style, data, ...rest } = props; if (className) { el.className = className; } if (style) { el.setAttribute("style", style); } for (const [key, value] of Object.entries(data ?? {})) { el.dataset[key] = value; } Object.assign(el, rest); append(el, ...children); return el; } export function append(el, ...children) { for (const child of children) { if (child === null || child === undefined || child === false) { continue; } el.appendChild(typeof child === "object" ? child : document.createTextNode(String(child))); } } export function replace(el, ...children) { el.replaceChildren(); append(el, ...children); } const copyIcon = ''; const copiedIcon = ''; export function copyable(pre) { const button = h("button", { type: "button", class: "copy", title: "Copy to the clipboard", ariaLabel: "Copy to the clipboard", innerHTML: copyIcon }); button.onclick = async () => { try { await navigator.clipboard.writeText(pre.textContent ?? ""); } catch (err) { void showAlert(errorText(err)); return; } button.innerHTML = copiedIcon; setTimeout(() => { button.innerHTML = copyIcon; }, 1500); }; return h("div", { class: "copyable" }, pre, button); } export function ago(iso) { const seconds = Math.round((Date.now() - Date.parse(iso)) / 1000); if (seconds < 60) { return "just now"; } if (seconds < 3600) { return `${Math.floor(seconds / 60)}m ago`; } if (seconds < 86400) { return `${Math.floor(seconds / 3600)}h ago`; } return `${Math.floor(seconds / 86400)}d ago`; } export function errorText(err) { return err instanceof Error ? err.message : String(err); } function modal(content, buttons, anchor = null) { const actions = buttons.map((b) => h("button", { type: "submit", value: b.value, class: b.class ?? "", formNoValidate: b.value === "cancel" }, b.label)); const body = typeof content === "string" ? [h("p", { class: "modal-text" }, content)] : content; const dialog = h("dialog", { class: "modal" }, h("form", { method: "dialog" }, ...body, h("div", { class: "modal-actions" }, ...actions))); for (const input of dialog.querySelectorAll("input")) { input.onkeydown = (e) => { if (e.key === "Enter") { e.preventDefault(); actions.at(-1)?.click(); } }; } if (anchor) { const rect = anchor.getBoundingClientRect(); dialog.classList.add("anchored"); dialog.style.top = `${rect.bottom + 4}px`; dialog.style.right = `${document.documentElement.clientWidth - rect.right}px`; } document.body.append(dialog); return new Promise((resolve) => { dialog.onclose = () => { dialog.remove(); resolve(dialog.returnValue); }; dialog.showModal(); const input = dialog.querySelector("input"); if (input) { input.focus(); input.setSelectionRange(0, input.value.length, "backward"); return; } actions.at(-1)?.focus(); }); } export async function showAlert(text) { await modal(text, [{ label: "OK", value: "ok", class: "primary" }]); } export async function showConfirm(content, action, danger = false, anchor = null) { const value = await modal(content, [ { label: "Cancel", value: "cancel" }, { label: action, value: "ok", class: danger ? "danger-primary" : "primary" }, ], anchor); return value === "ok"; } //# sourceMappingURL=dom.js.map