88 lines
3.1 KiB
TypeScript
88 lines
3.1 KiB
TypeScript
// Inspirado en https://gitea.nulo.in/Nulo/html.lua/src/commit/cb7e35dcca0e45b397baf628f5e1a162a2269638/html.lua
|
|
|
|
import { escape } from "html-escaper";
|
|
|
|
export interface VirtualElement {
|
|
__element_type: string;
|
|
things: Thing[];
|
|
}
|
|
export interface RawHtml {
|
|
__raw: string;
|
|
}
|
|
interface Attributes {
|
|
[key: string]: string | number;
|
|
}
|
|
type Thing = VirtualElement | RawHtml | Attributes | string;
|
|
export type Renderable = VirtualElement | RawHtml | string;
|
|
|
|
export const basicElement =
|
|
(__element_type: string) =>
|
|
(...things: Thing[]): VirtualElement => ({ __element_type, things });
|
|
export const raw = (html: string): RawHtml => ({ __raw: html });
|
|
|
|
export const doctype = basicElement("!doctype html");
|
|
export const head = basicElement("head");
|
|
export const body = basicElement("body");
|
|
export const meta = basicElement("meta");
|
|
export const title = basicElement("title");
|
|
export const link = basicElement("link");
|
|
export const script = basicElement("script");
|
|
export const metaUtf8 = meta({ charset: "utf-8" });
|
|
export const h1 = basicElement("h1");
|
|
export const h2 = basicElement("h2");
|
|
export const h3 = basicElement("h3");
|
|
export const h4 = basicElement("h4");
|
|
export const h5 = basicElement("h5");
|
|
export const h6 = basicElement("h6");
|
|
export const p = basicElement("p");
|
|
export const ul = basicElement("ul");
|
|
export const ol = basicElement("ol");
|
|
export const li = basicElement("li");
|
|
export const a = basicElement("a");
|
|
export const img = basicElement("img");
|
|
export const span = basicElement("span");
|
|
export const picture = basicElement("picture");
|
|
export const source = basicElement("source");
|
|
export const figure = basicElement("figure");
|
|
export const figcaption = basicElement("figcaption");
|
|
export const article = basicElement("article");
|
|
export const nav = basicElement("nav");
|
|
export const header = basicElement("header");
|
|
export const main = basicElement("main");
|
|
export const section = basicElement("section");
|
|
export const time = basicElement("time");
|
|
|
|
// TODO: actually escape
|
|
const escapeHTML = (string: string) => escape(string);
|
|
export const render = (...elements: Renderable[]): string => {
|
|
if (elements.length > 1) return elements.map((e) => render(e)).join("");
|
|
if (typeof elements[0] == "string") return escapeHTML(elements[0]);
|
|
if ("__raw" in elements[0]) return elements[0].__raw;
|
|
const { __element_type, things } = elements[0];
|
|
|
|
const attributes = things.filter(
|
|
(t) =>
|
|
!(typeof t == "string") && !("__element_type" in t) && !("__raw" in t)
|
|
);
|
|
const children = things.filter(
|
|
(t) =>
|
|
typeof t == "string" ||
|
|
("__element_type" in t && "things" in t) ||
|
|
"__raw" in t
|
|
) as Renderable[];
|
|
const selfClosing =
|
|
["meta", "img", "source", "link", "br"].includes(__element_type) ||
|
|
__element_type.startsWith("!"); // doctype
|
|
|
|
// TODO: escape attribute values
|
|
return `<${__element_type}${attributes.length ? " " : ""}${attributes
|
|
.flatMap((t) =>
|
|
Object.entries(t).map(([name, value]) => `${name}="${value}"`)
|
|
)
|
|
.join("")}${
|
|
selfClosing
|
|
? ">"
|
|
: `>${children.map((c) => render(c)).join("")}</${__element_type}>`
|
|
}`;
|
|
};
|