clone

tiny dependency-free nostr libraries for plain html, each under 5kb and named for an element. public domain

main
nobel / scripts / site.ts
site.ts 7.1K
1// Builds the static site in _site/: a home page, one page per library (from its README),2// the library files, the bundle, the demos, the license and llms.txt. Everything links on-site.3// Run `deno task bundle` first so dist/ exists.4import { brotliCompressSync } from "node:zlib";5import { copy } from "jsr:@std/fs@1/copy";6import { marked } from "npm:marked@15";7import { groups, libs, prefix } from "./libs.ts";89// Absolute links (llms.txt) point at GitHub Pages, or wherever `forge site deploy`10// serves the site, which it names in FORGE_SITE_URL. Every other link is relative.11const SITE = Deno.env.get("FORGE_SITE_URL") || "https://felinestatemachine.github.io/nobel/";12const root = new URL("../", import.meta.url), out = new URL("_site/", root);13const kb = (n: number) => (n / 1024).toFixed(1);14const read = (p: string) => Deno.readTextFile(new URL(p, root));1516await Deno.remove(out, { recursive: true }).catch(() => {});17await Deno.mkdir(out, { recursive: true });18for (const d of ["dist", "demo"]) await copy(new URL(d, root), new URL(d, out));19await copy(new URL("LICENSE", root), new URL("LICENSE.txt", out));2021// Per library: emoji and tagline from its README heading, sizes from its file.22const info: Record<string, { emoji: string; tagline: string; md: string; size: string }> = {};23for (const l of libs) {24 const md = await read(`${l.name}/README.md`), src = await Deno.readFile(new URL(`${l.name}/${l.name}.js`, root));25 const [, emoji, tagline] = md.split("\n")[0].match(/^# (\S+) \S+ - (.*)$/)!;26 info[l.name] = { emoji, tagline, md, size: `${kb(src.length)}/${kb(brotliCompressSync(src).length)}kb` };27 await Deno.mkdir(new URL(`${l.name}/`, out), { recursive: true });28 await Deno.writeFile(new URL(`${l.name}/${l.name}.js`, out), src);29}3031// Pages are listed in table order: by group, then atomic number.32const pages = Object.keys(groups).flatMap((g) => libs.filter((l) => l.group == g).sort((a, b) => a.z - b.z));3334const page = (title: string, body: string, prev?: string, next?: string) => `<!doctype html>35<html lang="en">36<head>37<meta charset="utf-8">38<meta name="viewport" content="width=device-width, initial-scale=1">39<title>${title}</title>40<style>41 :root { color-scheme:light dark; }42 body { max-width:46rem; margin:0 auto; padding:1rem; font:16px/1.5 system-ui, sans-serif; }43 nav { font-size:.9rem; line-height:1.9; }44 nav a { white-space:nowrap; margin-right:.6rem; text-decoration:none; }45 table { border-collapse:collapse; width:100%; font-size:.95rem; }46 th, td { text-align:left; padding:.25rem .5rem; border-bottom:1px solid #8884; vertical-align:top; }47 pre { background:#8881; padding:.6rem .8rem; overflow-x:auto; }48 code { font-size:.9em; }49 td:first-child code { white-space:nowrap; }50 .pager { display:flex; justify-content:space-between; margin:2rem 0 1rem; }51</style>52</head>53<body>54<nav><a href="./">⚗️ nobel</a> ${pages.map((l) => `<a href="${l.name}.html">${info[l.name].emoji} ${l.name}</a>`).join(" ")} <a href="llms.txt">🤖 llms.txt</a></nav>55<hr>56${body}57<div class="pager"><span>${prev ?? ""}</span><span>${next ?? ""}</span></div>58</body>59</html>60`;61const link = (l?: typeof libs[0], dir = "") =>62 l ? (dir == "prev" ? `&larr; ${info[l.name].emoji} <a href="${l.name}.html">${l.name}</a>` : `${info[l.name].emoji} <a href="${l.name}.html">${l.name}</a> &rarr;`) : `<a href="./">🏠 home</a>`;6364// One page per library.65for (const [i, l] of pages.entries()) {66 const html = await marked.parse(info[l.name].md);67 const src = `<p><a href="${l.name}/${l.name}.js">${l.name}.js</a> (${info[l.name].size} raw/brotli), prefix <code>${prefix(l)}-</code>${l.needs.length ? `, needs ${l.needs.map((n) => `<a href="${n}.html">${n}</a>`).join(", ")}` : ""}</p>`;68 await Deno.writeTextFile(new URL(`${l.name}.html`, out), page(`${l.name}.js - nobel`, html + src, link(pages[i - 1], "prev"), link(pages[i + 1])));69}7071// Home.72let rows = "";73for (const [g, about] of Object.entries(groups)) {74 rows += `<tr><th colspan="5">${g} <small>(${about})</small></th></tr>\n`;75 for (const l of pages.filter((l) => l.group == g)) {76 rows += `<tr><td><input type="checkbox" value="${l.name}"${g == "base" ? " checked" : ""}></td><td>${info[l.name].emoji} <a href="${l.name}.html">${l.name}</a></td><td><a href="${l.name}/${l.name}.js">${info[l.name].size}</a></td><td>${l.of}</td><td>${l.does}</td></tr>\n`;77 }78}79const min = await Deno.readFile(new URL("dist/nobel.min.js", root));80const order = JSON.stringify(libs.map((l) => [l.name, l.needs]));81const home = `<h1>⚗️ nobel</h1>82<p>nobel is a family of small nostr libraries for plain HTML, each a lightweight take on something bigger and each named for an element. Every library is one dependency-free script under 5kb, and they work together through DOM events, the way the <a href="https://fixiproject.org">fixi project</a> libraries do.</p>83<table>84<tr><th></th><th>library</th><th>size (raw/br)</th><th>miniature of</th><th>provides</th></tr>85${rows}</table>86<h2>Installing</h2>87<p>Plain <code>&lt;script&gt;</code> tags work in any order. Tick what you need above (what it needs is added) for one file from jsDelivr, in an order that also works with <code>async</code>:</p>88<pre id="out"></pre>89<p>Or download the files from the table, or take all of them in one file: <a href="dist/nobel.js">nobel.js</a>, <a href="dist/nobel.min.js">nobel.min.js</a> (${kb(min.length)}kb, ${kb(brotliCompressSync(min).length)}kb brotli).</p>90<h2>Demos</h2>91<p>Add <code>?relay=wss://...</code> to each: 📰 <a href="demo/">feed</a>, ✉️ <a href="demo/iodine.html">private messages</a>, 👻 <a href="demo/oganesson.html">presence, typing, cursors</a>, 🪞 <a href="demo/lanthanides.html">profiles</a>, 🔴 <a href="demo/europium.html">note embeds</a>.</p>92<h2>License</h2>93<p>Public domain: <a href="LICENSE.txt">the Unlicense</a>.</p>94<script>95 let libs = ${order}, boxes = [...document.querySelectorAll("input[type=checkbox]")]96 let show = ()=>{97 let want = new Set(boxes.filter((b)=>b.checked).map((b)=>b.value)), add = (n)=>libs.find((l)=>l[0] == n)[1].forEach((d)=>(want.add(d), add(d)))98 want.forEach(add)99 boxes.forEach((b)=>b.checked = want.has(b.value))100 let files = libs.filter((l)=>want.has(l[0])).map((l)=>"gh/FelineStateMachine/nobel@main/" + l[0] + "/" + l[0] + ".min.js")101 document.getElementById("out").textContent = files.length ? '<script src="https://cdn.jsdelivr.net/combine/' + files.join(",") + '"></' + 'script>' : ""102 }103 boxes.forEach((b)=>b.addEventListener("change", show))104 show()105</script>`;106await Deno.writeTextFile(new URL("index.html", out), page("nobel: small nostr libraries for plain HTML", home, "", link(pages[0])));107108// llms.txt, as fixi has.109await Deno.writeTextFile(new URL("llms.txt", out), `# nobel110111> A family of small, dependency-free nostr libraries for plain HTML, each named for an element. Each library is one script under 5kb; they compose through DOM events.112113${Object.entries(groups).map(([g, about]) => `## ${g} (${about})\n\n` + pages.filter((l) => l.group == g).map((l) => `- [${l.name}](${SITE}${l.name}.html): ${l.does}. Source: ${SITE}${l.name}/${l.name}.js`).join("\n")).join("\n\n")}114115## All in one116117- ${SITE}dist/nobel.js118`);119console.log(`_site/ built: home, ${pages.length} library pages, llms.txt`);