This commit is contained in:
Cat /dev/Nulo 2022-12-20 17:53:19 -03:00
commit 53e7c46749
6 changed files with 134 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules/
dist/

1
.npmrc Normal file
View file

@ -0,0 +1 @@
@nulo:registry=https://gitea.nulo.in/api/packages/nulo/npm/

81
html.ts Normal file
View file

@ -0,0 +1,81 @@
// Inspirado en https://gitea.nulo.in/Nulo/html.lua/src/commit/cb7e35dcca0e45b397baf628f5e1a162a2269638/html.lua
interface VirtualElement {
__element_type: string;
things: Thing[];
}
interface RawHtml {
__raw: string;
}
interface Attributes {
[key: string]: string | number;
}
type Thing = VirtualElement | RawHtml | Attributes | string;
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 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 main = basicElement("main");
// TODO: actually escape
const escapeHTML = (string: string) => 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}>`
}`;
};

18
package.json Normal file
View file

@ -0,0 +1,18 @@
{
"name": "@nulo/html.js",
"version": "0.0.2",
"description": "",
"main": "dist/html.js",
"files": [
"dist/**.js",
"dist/**.d.ts"
],
"scripts": {
"prepublish": "tsc"
},
"author": "Nulo <git@nulo.in>",
"devDependencies": {
"@types/node": "^18.11.17",
"typescript": "^4.9.4"
}
}

21
pnpm-lock.yaml Normal file
View file

@ -0,0 +1,21 @@
lockfileVersion: 5.4
specifiers:
'@types/node': ^18.11.17
typescript: ^4.9.4
devDependencies:
'@types/node': 18.11.17
typescript: 4.9.4
packages:
/@types/node/18.11.17:
resolution: {integrity: sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==}
dev: true
/typescript/4.9.4:
resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true

11
tsconfig.json Normal file
View file

@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"target": "es2022",
"module": "es2022",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"outDir": "dist/"
}
}