diff options
Diffstat (limited to 'src/server/template')
-rw-r--r-- | src/server/template/Base.ts | 117 | ||||
-rw-r--r-- | src/server/template/Page.ts | 23 | ||||
-rw-r--r-- | src/server/template/Post.ts | 48 | ||||
-rw-r--r-- | src/server/template/atom.ts | 114 | ||||
-rw-r--r-- | src/server/template/header.ts | 10 | ||||
-rw-r--r-- | src/server/template/html.ts | 602 | ||||
-rw-r--r-- | src/server/template/sitemap.ts | 31 | ||||
-rw-r--r-- | src/server/template/syntax.ts | 18 | ||||
-rw-r--r-- | src/server/template/table.ts | 32 | ||||
-rw-r--r-- | src/server/template/vdom.ts | 105 | ||||
-rw-r--r-- | src/server/template/xml.ts | 56 |
11 files changed, 1156 insertions, 0 deletions
diff --git a/src/server/template/Base.ts b/src/server/template/Base.ts new file mode 100644 index 0000000..e5128ee --- /dev/null +++ b/src/server/template/Base.ts @@ -0,0 +1,117 @@ +import metadata from "../metadata.js" +import curl from "../utils/curl.js" +import setStingRoute from "../utils/setStingRoute.js" +import isDefined from "../utils/isDefined.js" +import { Attribute, doctype, html as html_1, head, meta, title as title_1, link, script, body, header, nav, a, main, footer } from "./html.js" +import { c, node } from "./vdom.js" +import type { URL } from "url" + +export var pages: (ReturnType<typeof Base>)[] = [] + +type content<T extends node = node> = T | (() => T) + +interface BasePageI<T extends node = node> { + url?: string | URL | undefined, + content: content<T>, + date_mod?: Date | undefined, + date_pub?: Date +} + +export interface BaseI<T extends node = node> extends BasePageI<T> { + title?: string, + description?: string, + keywords?: string[], + isHighlight?: boolean +} + +export function content<Type extends BasePageI>({ data }: Attribute & { data: Type }): node { + return data.content instanceof Function ? data.content() : data.content +} + +function BasePage<Type extends BasePageI>(data: Type): Type & { + setupRoute(url?: URL): void; + render(): Promise<string>; + date_mod: Type["date_pub"]; + url: URL | undefined +} { + return { + ...data, + setupRoute(url?: URL): void { + url ??= this.url + if (isDefined(url)) { + setStingRoute(url.pathname, "html", this.render.bind(this)) + } else { + throw new Error(); + + } + }, + async render(): Promise<string> { + console.profile() + const a = doctype(c(content, { data: this })) + console.profileEnd() + return a + }, + date_mod: data.date_mod ?? data.date_pub, + url: isDefined(data.url) ? curl(data.url) : undefined, + } +} + +export default function Base<Type extends BaseI>(data: Type): ReturnType<typeof BasePage<Type>> { + const arg = { + ...data, + content() { + var { title, description, keywords, url, isHighlight = true } = this + if (!url) { + isHighlight = false + } + return c(html_1, { lang: metadata.language }, + c(head, {}, + ...(url ? [c(meta, { property: "og:locale", content: metadata.language })] : []), + c(title_1, { property: "og:title" }, title || metadata.title), + c(meta, { name: "theme-color", content: metadata.theme }), + ...(url ? [ + c(meta, { name: "description", property: "og:description", content: description || metadata.description }), + ...(Array.isArray(keywords) ? [c(meta, { name: "keywords", contents: keywords })] : []), + c(meta, { property: "og:url", content: url }), + c(link, { rel: "canonical", href: url }), + c(meta, { property: "og:site_name", content: metadata.title }), + c(meta, { name: "author", content: metadata.author.name }), + c(meta, { property: "og:type", content: "website" }), + c(meta, { name: "viewport", content: "width=device-width, initial-scale=1" }), + c(meta, { name: "twitter:card", content: "summary" }), + c(link, { rel: "alternate", href: metadata.feed.atom, type: "application/atom+xml", title: metadata.title }), + c(meta, { property: "og:image", content: "/favicon.svg?format=png&width=1024" }) + ] : []), + c(link, { rel: "icon", href: "/favicon.ico", sizes: "any" }), + c(link, { rel: "icon", href: "/favicon.svg", type: "image/svg+xml" }), + c(link, { rel: "apple-touch-icon", href: "/favicon.svg?format:png&width=192" }), + c(link, { rel: "manifest", href: "/app.webmanifest" }), + c(link, { rel: "stylesheet", href: "/index.css" }), + c(script, { type: "module", src: "/index.js" }), + ...(isHighlight ? [c(link, { rel: "stylesheet", href: "/syntax.css" })] : []) + ), + c(body, {}, + c(header, {}, c(nav, {}, + c(a, { href: "#", id: "nav-toogle" }, "Sudomsg"), + ...Object.entries({ + Home: "/", + Blog: "/blog", + About: "/about", + Git: "/cgit", + }).map(([name, href]) => c(a, { href, class: "navlinks" }, name)) + )), + c(main, {}, c(content, { data })), + c(footer, {}, + "Subscribe: ", + c(a, { href: metadata.feed.atom, type: "application/atom+xml" }, "Atom") + ) + ) + ) + } + } + const base = BasePage(arg) + if (isDefined(base.url)) { + pages.push(base) + } + return base +} diff --git a/src/server/template/Page.ts b/src/server/template/Page.ts new file mode 100644 index 0000000..ec76e97 --- /dev/null +++ b/src/server/template/Page.ts @@ -0,0 +1,23 @@ +import Base, { BaseI, content } from "./Base.js" +import h from "./header.js" +import { article } from "./html.js" +import { c, node } from "./vdom.js" + +export interface PageI<T extends node = node> extends BaseI<T> { + title: string +} + +export default function Page<Type extends PageI>(data: Type): ReturnType<typeof Base<Type>> { + const arg = { + ...data, + content() { + var { title } = this + return c(article, {}, + c(h, { level: 1 }, title), + c(content, { data }) + ) + } + + } + return Base(arg) +}
\ No newline at end of file diff --git a/src/server/template/Post.ts b/src/server/template/Post.ts new file mode 100644 index 0000000..c348f8b --- /dev/null +++ b/src/server/template/Post.ts @@ -0,0 +1,48 @@ +import schema from "../utils/schema.js" +import metadata from "../metadata.js" +import Base, { content } from "./Base.js" +import h from "./header.js" +import slugify from "@sindresorhus/slugify" +import { c } from "./vdom.js" +import type { PageI } from "./Page.js" +import { article, small, time, span, a, p, div } from "./html.js" + +export var posts: ReturnType<typeof Post>[] = [] + +interface PostI extends Omit<PageI, "url"> { + title: string, + description: string, + date_pub: Date, + date_mod?: Date +} + +export default function Post<Type extends PostI>(data: Type): ReturnType<typeof Base<Type & {url: string;}>> { + var args = { + ...data, + content() { + var { date_mod, title, date_pub, description } = this + return c(article, { itemscope: true, itemtype: schema("BlogPosting") }, + c(h, { level: 1, itemprop: "headline" }, title), + c(small, {}, + ...(date_mod ? [ + c(time, { datetime: date_mod.toISOString(), itemprop: "dateModified" }, date_mod.toDateString()), + "- Modified " + ] : []), + c(time, { datetime: date_pub.toISOString(), itemprop: "datePublished" }, date_pub.toDateString()) + , " - ", + c(span, { itemprop: "author", itemscope: true, itemtype: schema("Person") }, + c(a, { href: metadata.author.url, rel: "author", itemprop: "url" }, + c(span, { itemprop: "name" }, metadata.author.name)) + ), + c(p, { itemprop: "abstract" }, description) + ), + c(div, { itemprop: "articleBody" }, c(content, { data })) + ) + }, + url: `/posts/${slugify(data.title)}` + } + const post = Base(args) + posts.push(post) + return post +} + diff --git a/src/server/template/atom.ts b/src/server/template/atom.ts new file mode 100644 index 0000000..82c9fe4 --- /dev/null +++ b/src/server/template/atom.ts @@ -0,0 +1,114 @@ +import type { element, node } from "./vdom.js"; +import t, { Base, Lang, Attribute as Attr } from "./xml.js"; + +export interface Attribute extends Attr, Base, Lang { } + +export function feed(attr: Attribute, + ...content: element<"id" | "title" | "updated" | "author" | + "link" | "author" | "category" | "contributor" | "generator" + | "icon" | "logo" | "rights" | "subtitle" | "entry">[] +) { + return t("feed", attr, ...content); +} + +export function entry( + attr: Attribute, + ...content: element< + "id" | "title" | "updated" | "author" | "content" | "link" | + "summary" | "category" | "contributor" | "rights" | "published" | "source">[] +) { + return t("entry", attr, ...content); +} + +export function author(attr: Attribute, ...content: [element<"name" | "email">, element<"name" | "email">]) { + return t("author", attr, ...content); +} + +export function contributor(attr: Attribute, ...content: [element<"name" | "email">, element<"name" | "email">]) { + return t("contributor", attr, ...content); +} + +export function id({ id, ...prop }: Attribute & { id: URL }) { + return t("id", prop, id.href); +} + +export function updated({ date, ...prop }: Attribute & { date: Date }) { + return t("updated", prop, date.toISOString()); +} + +export function update({ date, ...prop }: Attribute & { date: Date } ) { + return t("update", prop, date.toISOString()); +} + +export function icon(attr: Attribute, ...content: string[]) { + return t("icon", attr, ...content); +} + +export function logo(attr: Attribute, ...content: string[]) { + return t("logo", attr, ...content); +} + +export function name(attr: Attribute, content: string) { + return t("name", attr, content); +} + +export function email(attr: Attribute, content: string) { + return t("email", attr, content); +} + +interface TextAttribute extends Attribute { + type?: string; +} + +export function title(attr: TextAttribute, ...content: node[]) { + return t("title", attr, ...content); +} + +export function subtitle(attr: TextAttribute, ...content: string[]) { + return t("subtitle", attr, ...content); +} + +export function summary(attr: TextAttribute, ...content: string[]) { + return t("summary", attr, ...content); +} + +export function rights(attr: TextAttribute, ...content: string[]) { + return t("rights", attr, ...content); +} + +export function content(attr: TextAttribute, ...child: node[]) { + return t("content", attr, ...child); +} + + +interface generatorAttribute extends Attribute { + uri?: string; + version?: string +} + +export function generator(attr: generatorAttribute, ...content: string[]) { + return t("generator", attr, ...content); +} + +interface linkAttribute extends Attribute { + href: URL; + rel?: "alternate" | "enclosure" | "self" | "via"; + type?: string; + hreflang?: string; + title?: string; + length?: number; +} + +export function link(attr: linkAttribute) { + return t("link", { ...attr, href: attr.href.href, length: "" + (attr.length ?? "") }); +} + +interface catAttribute extends Attribute { + term: string; + scheme?: URL; + label?: string; +} + +export function category(attr: catAttribute) { + return t("category", { ...attr, href: attr.scheme?.href }); +} diff --git a/src/server/template/header.ts b/src/server/template/header.ts new file mode 100644 index 0000000..04da87d --- /dev/null +++ b/src/server/template/header.ts @@ -0,0 +1,10 @@ +import slugify from "@sindresorhus/slugify" +import t, { Attribute, getText, node } from "./vdom.js" + +interface head extends Attribute { + level: 1 | 2 | 3 | 4 | 5 | 6 +} + +export default function ({ level, ...attr }: head, ...content: node[]) { + return t(`h${level}`, { id: slugify(getText(content)), ...attr }, ...content) +} diff --git a/src/server/template/html.ts b/src/server/template/html.ts new file mode 100644 index 0000000..ff0a0cc --- /dev/null +++ b/src/server/template/html.ts @@ -0,0 +1,602 @@ +import isDefined from "../utils/isDefined.js" +import tag, { render as rend, prenderdata, node, Attribute as Attr, element } from "./vdom.js" + +export function doctype(...content: node[]) { + return `<!DOCTYPE html>${render(content).content}` +} + +export const render = rend("html", function (node, content) { + const close = [ + "img", + "input", + "br", + "hr", + "frame", + "area", + "base", + "basefont", + "col", + "isindex", + "link", + "meta", + "param", + "html", + "body", + "p", + "li", + "dt", + "dd", + "option", + "thead", + "th", + "tbody", + "tr", + "td", + "tfoot", + "colgroup", + ].includes(node.name.toLowerCase()) ? undefined : `</${node.name}>` + var list = Object.entries(node.attr).map(([prop, val]) => { + if (!isDefined(val)) { + return "" + } + + if (typeof val == "boolean") { + return val ? prop : "" + } + var v: string = Array.isArray(val) ? val.join(" ") : "" + val + + if (/[\s?"'<=>`]/.test(v)) { + v = `"${v}"` + } + return `${prop}=${v}` + }).join(" ") + if (list) { + list = ` ${list} ` + } + const open = `<${node.name}${list}>` + return `${open}${content ?? ""}${close ?? ""}` +}) + +export const data = prenderdata("html") + +export interface Attribute extends Attr { + class?: string | string[], + id?: string, + accesskey?: string + dir?: "ltr" | "rtr" | "auto" + draggable?: "true" | "false" | "auto" + lang?: string + role?: string + is?: string + itemid?: URL + itemprop?: string + itemref?: string[] + itemtype?: URL + itemscope?: boolean + property?: string +} + +export default function t<T extends string = string>(name: T, attr?: Attribute, ...content: node[]) { + return tag(name, { + ...attr, + itemtype: attr?.itemtype?.href, + itemid: attr?.itemtype?.href + }, ...content) +} + +export function body(attr: Attribute, ...content: node[]) { + return t("body", attr, ...content); +} + +export function head(attr: Attribute, ...content: element[]) { + return t("head", attr, ...content); +} + +export function html(attr: Attribute, ...content: node[]) { + return t("html", attr, ...content); +} + +export function a(attr: Attribute, ...content: node[]) { + return t("a", attr, ...content); +} + + +export function abbr(attr: Attribute, ...content: node[]) { + return t("abbr", attr, ...content); +} + + +export function address(attr: Attribute, ...content: node[]) { + return t("address", attr, ...content); +} + + +export function area(attr: Attribute) { + return t("area", attr); +} + + +export function article(attr: Attribute, ...content: node[]) { + return t("article", attr, ...content); +} + + +export function aside(attr: Attribute, ...content: node[]) { + return t("aside", attr, ...content); +} + + +export function audio(attr: Attribute, ...content: node[]) { + return t("audio", attr, ...content); +} + + +export function b(attr: Attribute, ...content: node[]) { + return t("b", attr, ...content); +} + + +export function base(attr: Attribute, ...content: node[]) { + return t("base", attr, ...content); +} + + +export function bdi(attr: Attribute, ...content: node[]) { + return t("bdi", attr, ...content); +} + + +export function bdo(attr: Attribute, ...content: node[]) { + return t("bdo", attr, ...content); +} + + +export function blockquote(attr: Attribute, ...content: node[]) { + return t("blockquote", attr, ...content); +} + +export function br(attr: Attribute) { + return t("br", attr); +} + + +export function button(attr: Attribute, ...content: node[]) { + return t("button", attr, ...content); +} + + +export function canvas(attr: Attribute, ...content: node[]) { + return t("canvas", attr, ...content); +} + + +export function caption(attr: Attribute, ...content: node[]) { + return t("caption", attr, ...content); +} + + +export function cite(attr: Attribute, ...content: node[]) { + return t("cite", attr, ...content); +} + + +export function code(attr: Attribute, ...content: node[]) { + return t("code", attr, ...content); +} + + +export function col(attr: Attribute) { + return t("col", attr); +} + + +export function colgroup(attr: Attribute, ...content: node[]) { + return t("colgroup", attr, ...content); +} + + +export function hdata(attr: Attribute, ...content: node[]) { + return t("data", attr, ...content); +} + + +export function datalist(attr: Attribute, ...content: node[]) { + return t("datalist", attr, ...content); +} + + +export function dd(attr: Attribute, ...content: node[]) { + return t("dd", attr, ...content); +} + + +export function del(attr: Attribute, ...content: node[]) { + return t("del", attr, ...content); +} + + +export function details(attr: Attribute, ...content: node[]) { + return t("details", attr, ...content); +} + + +export function dfn(attr: Attribute, ...content: node[]) { + return t("dfn", attr, ...content); +} + + +export function dialog(attr: Attribute, ...content: node[]) { + return t("dialog", attr, ...content); +} + + +export function div(attr: Attribute, ...content: node[]) { + return t("div", attr, ...content); +} + + +export function dl(attr: Attribute, ...content: node[]) { + return t("dl", attr, ...content); +} + + +export function dt(attr: Attribute, ...content: node[]) { + return t("dt", attr, ...content); +} + + +export function em(attr: Attribute, ...content: node[]) { + return t("em", attr, ...content); +} + + +export function embed(attr: Attribute) { + return t("embed", attr); +} + + +export function fieldset(attr: Attribute, ...content: node[]) { + return t("fieldset", attr, ...content); +} + + +export function figcaption(attr: Attribute, ...content: node[]) { + return t("figcaption", attr, ...content); +} + + +export function figure(attr: Attribute, ...content: node[]) { + return t("figure", attr, ...content); +} + + +export function footer(attr: Attribute, ...content: node[]) { + return t("footer", attr, ...content); +} + + +export function form(attr: Attribute, ...content: node[]) { + return t("form", attr, ...content); +} + +export function header(attr: Attribute, ...content: node[]) { + return t("header", attr, ...content); +} + + +export function hgroup(attr: Attribute, ...content: node[]) { + return t("hgroup", attr, ...content); +} + + +export function hr(attr: Attribute) { + return t("hr", attr); +} + +export function i(attr: Attribute, ...content: node[]) { + return t("i", attr, ...content); +} + + +export function iframe(attr: Attribute, ...content: node[]) { + return t("iframe", attr, ...content); +} + + +export function img(attr: Attribute) { + return t("img", attr); +} + + +export function input(attr: Attribute) { + return t("input", attr); +} + + +export function ins(attr: Attribute, ...content: node[]) { + return t("ins", attr, ...content); +} + + +export function kbd(attr: Attribute, ...content: node[]) { + return t("kbd", attr, ...content); +} + + +export function label(attr: Attribute, ...content: node[]) { + return t("label", attr, ...content); +} + + +export function legend(attr: Attribute, ...content: node[]) { + return t("legend", attr, ...content); +} + + +export function li(attr: Attribute, ...content: node[]) { + return t("li", attr, ...content); +} + + +export function link(attr: Attribute) { + return t("link", attr); +} + + +export function main(attr: Attribute, ...content: node[]) { + return t("main", attr, ...content); +} + + +export function map(attr: Attribute, ...content: node[]) { + return t("map", attr, ...content); +} + + +export function mark(attr: Attribute, ...content: node[]) { + return t("mark", attr, ...content); +} + + +export function menu(attr: Attribute, ...content: node[]) { + return t("menu", attr, ...content); +} + + +export function meta(attr: Attribute) { + return t("meta", attr); +} + + +export function meter(attr: Attribute, ...content: node[]) { + return t("meter", attr, ...content); +} + + +export function nav(attr: Attribute, ...content: node[]) { + return t("nav", attr, ...content); +} + + +export function noscript(attr: Attribute, ...content: node[]) { + return t("noscript", attr, ...content); +} + + +export function object(attr: Attribute, ...content: node[]) { + return t("object", attr, ...content); +} + + +export function ol(attr: Attribute, ...content: node[]) { + return t("ol", attr, ...content); +} + + +export function optgroup(attr: Attribute, ...content: node[]) { + return t("optgroup", attr, ...content); +} + + +export function option(attr: Attribute, ...content: node[]) { + return t("option", attr, ...content); +} + + +export function output(attr: Attribute, ...content: node[]) { + return t("output", attr, ...content); +} + + +export function p(attr: Attribute, ...content: node[]) { + return t("p", attr, ...content); +} + + +export function picture(attr: Attribute, ...content: node[]) { + return t("picture", attr, ...content); +} + + +export function pre(attr: Attribute, ...content: node[]) { + return t("pre", attr, ...content); +} + + +export function progress(attr: Attribute, ...content: node[]) { + return t("progress", attr, ...content); +} + + +export function q(attr: Attribute, ...content: node[]) { + return t("q", attr, ...content); +} + + +export function rp(attr: Attribute, ...content: node[]) { + return t("rp", attr, ...content); +} + + +export function rt(attr: Attribute, ...content: node[]) { + return t("rt", attr, ...content); +} + + +export function ruby(attr: Attribute, ...content: node[]) { + return t("ruby", attr, ...content); +} + + +export function s(attr: Attribute, ...content: node[]) { + return t("s", attr, ...content); +} + + +export function samp(attr: Attribute, ...content: node[]) { + return t("samp", attr, ...content); +} + + +export function script(attr: Attribute, ...content: node[]) { + return t("script", attr, ...content); +} + + +export function section(attr: Attribute, ...content: node[]) { + return t("section", attr, ...content); +} + + +export function select(attr: Attribute, ...content: node[]) { + return t("select", attr, ...content); +} + + +export function slot(attr: Attribute, ...content: node[]) { + return t("slot", attr, ...content); +} + + +export function small(attr: Attribute, ...content: node[]) { + return t("small", attr, ...content); +} + + +export function source(attr: Attribute) { + return t("source", attr); +} + + +export function span(attr: Attribute, ...content: node[]) { + return t("span", attr, ...content); +} + + +export function strong(attr: Attribute, ...content: node[]) { + return t("strong", attr, ...content); +} + + +export function style(attr: Attribute, ...content: node[]) { + return t("style", attr, ...content); +} + + +export function sub(attr: Attribute, ...content: node[]) { + return t("sub", attr, ...content); +} + + +export function summary(attr: Attribute, ...content: node[]) { + return t("summary", attr, ...content); +} + + +export function sup(attr: Attribute, ...content: node[]) { + return t("sup", attr, ...content); +} + + +export function table(attr: Attribute, ...content: node[]) { + return t("table", attr, ...content); +} + + +export function tbody(attr: Attribute, ...content: node[]) { + return t("tbody", attr, ...content); +} + + +export function td(attr: Attribute, ...content: node[]) { + return t("td", attr, ...content); +} + + +export function template(attr: Attribute, ...content: node[]) { + return t("template", attr, ...content); +} + + +export function textarea(attr: Attribute, ...content: node[]) { + return t("textarea", attr, ...content); +} + + +export function tfoot(attr: Attribute, ...content: node[]) { + return t("tfoot", attr, ...content); +} + + +export function th(attr: Attribute, ...content: node[]) { + return t("th", attr, ...content); +} + + +export function thead(attr: Attribute, ...content: node[]) { + return t("thead", attr, ...content); +} + + +export function time(attr: Attribute, ...content: node[]) { + return t("time", attr, ...content); +} + + +export function title(attr: Attribute, ...content: node[]) { + return t("title", attr, ...content); +} + + +export function tr(attr: Attribute, ...content: node[]) { + return t("tr", attr, ...content); +} + + +export function track(attr: Attribute) { + return t("track", attr); +} + + +export function u(attr: Attribute, ...content: node[]) { + return t("u", attr, ...content); +} + + +export function ul(attr: Attribute, ...content: node[]) { + return t("ul", attr, ...content); +} + + +export function hvar(attr: Attribute, ...content: node[]) { + return t("var", attr, ...content); +} + +export function video(attr: Attribute, ...content: node[]) { + return t("video", attr, ...content); +} + + +export function wbr(attr: Attribute) { + return t("wbr", attr); +}
\ No newline at end of file diff --git a/src/server/template/sitemap.ts b/src/server/template/sitemap.ts new file mode 100644 index 0000000..0a59ec5 --- /dev/null +++ b/src/server/template/sitemap.ts @@ -0,0 +1,31 @@ +import type { element } from "./vdom.js"; +import t, { Attribute } from "./xml.js"; + +export function urlset(attr: Attribute, ...content: element<'url'>[]) { + return t("urlset", attr, ...content); +} + +export function url( + attr: Attribute, + ...content: element<"loc" | "lastmod" | "changefreq" | "priority">[] +) { + return t("url", attr, ...content); +} + +export function loc(attr: Attribute, ...content: string[]) { + return t("loc", attr, ...content); +} + +export function lastmod(attr: Attribute, content: string) { + return t("lastmod", attr, content); +} + +export function changefreq(attr: Attribute, + content: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never" +) { + return t("changefreq", attr, content); +} + +export function priority(attr: Attribute, content: string) { + return t("priority", attr, content); +}
\ No newline at end of file diff --git a/src/server/template/syntax.ts b/src/server/template/syntax.ts new file mode 100644 index 0000000..82b37e8 --- /dev/null +++ b/src/server/template/syntax.ts @@ -0,0 +1,18 @@ +import hljs from "highlight.js" +import { data, Attribute, code as Code } from "./html.js" +import { c } from "./vdom.js" + +interface Syntax extends Attribute { + lang: string +} + +export default function code({ lang, ...attr }: Syntax, content: string) { + return c( + Code, + { + class: attr.class ?? "hljs", + ...attr + }, + data(hljs.highlight(content,{ language: lang }).value) + ) +}
\ No newline at end of file diff --git a/src/server/template/table.ts b/src/server/template/table.ts new file mode 100644 index 0000000..34f2771 --- /dev/null +++ b/src/server/template/table.ts @@ -0,0 +1,32 @@ +import { c, node } from "./vdom.js"; +import { Attribute, caption, table as Table, td, th, thead, tr } from "./html.js"; + +interface TableAttr extends Attribute { + header: boolean; + data: node[][]; + caption?: string; +} + +export default function table({ header, data, caption: captio, ...attr }: TableAttr) { + const capt = captio ? c(caption, {}, captio) : undefined; + if (header) { + const [head, ...tbldata] = data; + return c(Table, attr, + ...tbldata.map( + row => c(tr, {}, ...row.map( + key => c(td, {}, key ?? "") + )) + ), + c(thead, {}, c(tr, {}, ...(head ?? []).map(e => c(th, {}, e ?? "")))), + ...(capt ? [capt] : []) + ); + } else { + return c(Table, attr, ...data.map( + row => c(tr, {}, ...row.map( + key => c(td, {}, key ?? "") + )) + ), + ...(capt ? [capt] : []) + ); + } +} diff --git a/src/server/template/vdom.ts b/src/server/template/vdom.ts new file mode 100644 index 0000000..899565b --- /dev/null +++ b/src/server/template/vdom.ts @@ -0,0 +1,105 @@ +export type component< + T extends Attribute = Attribute, + U extends node[] = node[], + R extends node = node +> = ((attr: T, ...content: U) => R) + +export interface Attribute { + [key: string]: unknown, +} + +export interface element<T extends string = string> { + name: T, + attr: Attribute, + content: node[], +} + +export function iselement<T extends string = string>(e: unknown): e is element<T> { + return typeof e === "object" && e !== null && "name" in e; +} + +export interface typed_data<T extends string = string> { + content: string, + type: T +} + +export function istyped<T extends string = string>(e: unknown): e is typed_data<T> { + return typeof e === "object" && e !== null && "type" in e; +} + +export type node = element | typed_data | node[] | string + +export function c< + T extends Attribute = Attribute, + U extends node[] = node[], + R extends node = node +>(name: component<T, U, R>, attr: T, ...content: U): R { + return name(attr, ...content) +} + +export default function t<T extends string = string>(name: T, attr?: Attribute, ...content: node[]): element<T> { + return { + name, + attr: attr ?? ({} as Attribute), + content + } +} + +export function frag<T extends node[] = node[]>(_attr: Attribute, ...content: T) { + return content +} + +export function join({sep = ""}: Attribute & {sep?: string}, ...content: string[]) { + return content.join(sep) +} + +export function prenderdata<T extends string = string>(type: T) { + return function (content: string): typed_data<T> { + return { + content, + type + } + } +} + +export function render<T extends string = string>(type: T, rendFunc: (node: element, content: string) => string) { + const data = prenderdata(type) + return function rend(Node: node): typed_data<T> { + if (Array.isArray(Node)) { + return data(Node.map(element => rend(element).content ?? "").join("")) + } + + if (typeof Node == "string") { + return data(Node) + } + + if (iselement(Node)) { + return data(rendFunc(Node, rend(Node.content).content)) + } + + if (istyped<T>(Node)) { + if (Node.type == type) { + return Node + } else { + throw new Error(Node.type + "is not valid. The type must be:" + type) + } + } + throw new TypeError("" + Node) + } +} + +export function getText(node: node): string { + if (Array.isArray(node)) { + return node.map(element => getText(element) ?? "").join("") + } + + if (typeof node == "string") { + return node + } + + if (iselement(node)) { + return getText(node.content) + } + + throw new TypeError("" + node) +} diff --git a/src/server/template/xml.ts b/src/server/template/xml.ts new file mode 100644 index 0000000..fa6dbb4 --- /dev/null +++ b/src/server/template/xml.ts @@ -0,0 +1,56 @@ +import isDefined from "../utils/isDefined.js" +import tag, { render, prenderdata, node, element, Attribute as Attr } from "./vdom.js" + +type encoding = "UTF-8" | "UTF-16" | "ISO-10646-UCS-2" | "ISO-10646-UCS-4" | "ISO-8859-1" | "ISO-8859-2" | "ISO-8859-3" | "ISO-8859-4" | + "ISO-8859-5" | "ISO-8859-6" | "ISO-8859-7" | "ISO-8859-8" | "ISO-8859-9" | "ISO-2022-JP" | "Shift_JIS" | "EUC-JP" + +interface xmlDeclaration { + version?: "1.0", + encoding?: encoding + standalone?: boolean +} + +export function doctype(options: xmlDeclaration, content: node): string { + const version = `version=${options.version ?? `"1.0"`} ` + const encoding = isDefined(options.encoding) ? `encoding="${options.encoding}" ` : "" + const standalone = isDefined(options.standalone) ? `standalone="${options.standalone ? "yes" : "no"}" ` : "" + return `<?xml ${version}${encoding}${standalone}?>${xmlrender(content).content}` +} + +export const xmlrender = render("xml", function (node: element, content: string) { + var list = Object.entries(node.attr).map(([prop, val]): string => { + if (!isDefined(val)) { + return "" + } + var v: string = Array.isArray(val) ? val.join(" ") : "" + val + return `${prop}="${v}"` + }).join(" ") + if (list) { + list = ` ${list} ` + } + const open = `<${node.name}${list}>` + const close = `</${node.name}>` + if (content || !close) { + return `${open}${content || ""}${close}` + } else { + return `<${node.name}${list}/>` + } +}) + +export const xmldata = prenderdata("xml") + +export interface Attribute extends Attr { + xmlns?: URL +} + +export interface Base extends Attribute { + ["xml:base"]?: string +} + +export interface Lang extends Attribute { + ["xml:lang"]?: string +} + +export default function t<T extends string = string>(name: T, attr?: Attribute, ...content: node[]) { + return tag(name, { ...attr, xmlns: attr?.xmlns?.href }, ...content) +} |