95 lines
3.3 KiB
JavaScript
95 lines
3.3 KiB
JavaScript
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);
|
||
}
|
||
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(text, buttons, anchor = null) {
|
||
const actions = buttons.map((b) => h("button", { type: "submit", value: b.value, class: b.class ?? "" }, b.label));
|
||
const dialog = h("dialog", { class: "modal" }, h("form", { method: "dialog" }, h("p", { class: "modal-text" }, text), h("div", { class: "modal-actions" }, ...actions)));
|
||
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();
|
||
actions.at(-1)?.focus();
|
||
});
|
||
}
|
||
export function openPanel(title, mount) {
|
||
if (document.querySelector("dialog.panel[open]")) {
|
||
return;
|
||
}
|
||
const close = h("button", { type: "button", class: "panel-close", title: "Close (Esc)" }, "×");
|
||
const body = h("div", { class: "panel-body" });
|
||
const dialog = h("dialog", { class: "modal panel" }, h("header", { class: "panel-head" }, h("h2", {}, title), close), body);
|
||
close.onclick = () => dialog.close();
|
||
dialog.onclick = (e) => {
|
||
if (e.target === dialog || e.target.closest("a[href^='#']")) {
|
||
dialog.close();
|
||
}
|
||
};
|
||
document.body.append(dialog);
|
||
const dispose = mount(body);
|
||
dialog.onclose = () => {
|
||
dispose();
|
||
dialog.remove();
|
||
};
|
||
dialog.showModal();
|
||
}
|
||
export async function showAlert(text) {
|
||
await modal(text, [{ label: "OK", value: "ok", class: "primary" }]);
|
||
}
|
||
export async function showConfirm(text, action, danger = false, anchor = null) {
|
||
const value = await modal(text, [
|
||
{ label: "Cancel", value: "cancel" },
|
||
{ label: action, value: "ok", class: danger ? "danger-primary" : "primary" },
|
||
], anchor);
|
||
return value === "ok";
|
||
}
|
||
//# sourceMappingURL=dom.js.map
|