diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-04-09 20:27:54 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-04-09 22:24:37 +0900 |
| commit | dbc3e0dfd893435f31cca39873f7ba9bf13b93a6 (patch) | |
| tree | 2cee4227ec6a995a9ddc99894dce604523833dc7 /vhosts/blog/nuldoc-src/ndoc | |
| parent | bba1212ab46ed85c2ed3b646f2362bdbb1f45b63 (diff) | |
| download | nsfisis.dev-dbc3e0dfd893435f31cca39873f7ba9bf13b93a6.tar.gz nsfisis.dev-dbc3e0dfd893435f31cca39873f7ba9bf13b93a6.tar.zst nsfisis.dev-dbc3e0dfd893435f31cca39873f7ba9bf13b93a6.zip | |
feat(blog/nuldoc): change format of nuldoc builder from .ndoc to .dj
Diffstat (limited to 'vhosts/blog/nuldoc-src/ndoc')
| -rw-r--r-- | vhosts/blog/nuldoc-src/ndoc/document.ts | 66 | ||||
| -rw-r--r-- | vhosts/blog/nuldoc-src/ndoc/parse.ts | 33 | ||||
| -rw-r--r-- | vhosts/blog/nuldoc-src/ndoc/to_html.ts | 292 |
3 files changed, 0 insertions, 391 deletions
diff --git a/vhosts/blog/nuldoc-src/ndoc/document.ts b/vhosts/blog/nuldoc-src/ndoc/document.ts deleted file mode 100644 index dfb6d03b..00000000 --- a/vhosts/blog/nuldoc-src/ndoc/document.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { join } from "@std/path"; -import { Config } from "../config.ts"; -import { NuldocError } from "../errors.ts"; -import { Revision, stringToDate } from "../revision.ts"; -import { Element, findFirstChildElement } from "../dom.ts"; -import { z } from "zod/mod.ts"; - -export const PostMetadataSchema = z.object({ - article: z.object({ - uuid: z.string(), - title: z.string(), - description: z.string(), - tags: z.array(z.string()), - revisions: z.array(z.object({ - date: z.string(), - remark: z.string(), - isInternal: z.boolean().optional(), - })), - }), -}); - -export type PostMetadata = z.infer<typeof PostMetadataSchema>; - -export type Document = { - root: Element; - sourceFilePath: string; - uuid: string; - link: string; - title: string; - description: string; // TODO: should it be markup text? - tags: string[]; - revisions: Revision[]; -}; - -export function createNewDocumentFromRootElement( - root: Element, - meta: PostMetadata, - sourceFilePath: string, - config: Config, -): Document { - const article = findFirstChildElement(root, "article"); - if (!article) { - throw new NuldocError( - `[nuldoc.new] <article> element not found`, - ); - } - - const cwd = Deno.cwd(); - const contentDir = join(cwd, config.locations.contentDir); - const link = sourceFilePath.replace(contentDir, "").replace(".xml", "/"); - return { - root: root, - sourceFilePath: sourceFilePath, - uuid: meta.article.uuid, - link: link, - title: meta.article.title, - description: meta.article.description, - tags: meta.article.tags, - revisions: meta.article.revisions.map((r, i) => ({ - number: i, - date: stringToDate(r.date), - remark: r.remark, - isInternal: !!r.isInternal, - })), - }; -} diff --git a/vhosts/blog/nuldoc-src/ndoc/parse.ts b/vhosts/blog/nuldoc-src/ndoc/parse.ts deleted file mode 100644 index 4bb96f4d..00000000 --- a/vhosts/blog/nuldoc-src/ndoc/parse.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { parse as parseToml } from "@std/toml"; -import { Config } from "../config.ts"; -import { parseXmlString } from "../xml.ts"; -import { - createNewDocumentFromRootElement, - Document, - PostMetadata, - PostMetadataSchema, -} from "./document.ts"; -import toHtml from "./to_html.ts"; - -export async function parseNulDocFile( - filePath: string, - config: Config, -): Promise<Document> { - try { - const fileContent = await Deno.readTextFile(filePath); - const parts = fileContent.split(/^---$/m); - const meta = parseMetadata(parts[1]); - const root = parseXmlString("<?xml ?>" + parts[2]); - const doc = createNewDocumentFromRootElement(root, meta, filePath, config); - return await toHtml(doc); - } catch (e) { - if (e instanceof Error) { - e.message = `${e.message} in ${filePath}`; - } - throw e; - } -} - -function parseMetadata(s: string): PostMetadata { - return PostMetadataSchema.parse(parseToml(s)); -} diff --git a/vhosts/blog/nuldoc-src/ndoc/to_html.ts b/vhosts/blog/nuldoc-src/ndoc/to_html.ts deleted file mode 100644 index a82f0333..00000000 --- a/vhosts/blog/nuldoc-src/ndoc/to_html.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { BundledLanguage, bundledLanguages, codeToHtml } from "shiki"; -import { Document } from "./document.ts"; -import { NuldocError } from "../errors.ts"; -import { - addClass, - Element, - forEachChild, - forEachChildRecursively, - forEachChildRecursivelyAsync, - Node, - RawHTML, - Text, -} from "../dom.ts"; - -export default async function toHtml(doc: Document): Promise<Document> { - removeUnnecessaryTextNode(doc); - transformLinkLikeToAnchorElement(doc); - transformSectionIdAttribute(doc); - setSectionTitleAnchor(doc); - transformSectionTitleElement(doc); - transformNoteElement(doc); - addAttributesToExternalLinkElement(doc); - setDefaultLangAttribute(doc); - traverseFootnotes(doc); - await transformAndHighlightCodeBlockElement(doc); - return doc; -} - -function removeUnnecessaryTextNode(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element") { - return; - } - - let changed = true; - while (changed) { - changed = false; - if (n.children.length === 0) { - break; - } - const firstChild = n.children[0]; - if (firstChild.kind === "text" && firstChild.content.trim() === "") { - n.children.shift(); - changed = true; - } - if (n.children.length === 0) { - break; - } - const lastChild = n.children[n.children.length - 1]; - if (lastChild.kind === "text" && lastChild.content.trim() === "") { - n.children.pop(); - changed = true; - } - } - }); -} - -function transformLinkLikeToAnchorElement(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if ( - n.kind !== "element" || n.name === "a" || n.name === "code" || - n.name === "codeblock" - ) { - return; - } - - const newChildren: Node[] = []; - for (const child of n.children) { - if (child.kind !== "text") { - newChildren.push(child); - continue; - } - let restContent = child.content; - while (restContent !== "") { - const match = /^(.*?)(https?:\/\/[^ \n]+)(.*)$/s.exec(restContent); - if (!match) { - newChildren.push({ kind: "text", content: restContent, raw: false }); - restContent = ""; - break; - } - const [_, prefix, url, suffix] = match; - newChildren.push({ kind: "text", content: prefix, raw: false }); - newChildren.push({ - kind: "element", - name: "a", - attributes: new Map([["href", url]]), - children: [{ kind: "text", content: url, raw: false }], - }); - restContent = suffix; - } - } - n.children = newChildren; - }); -} - -function transformSectionIdAttribute(doc: Document) { - const sectionStack: string[] = []; - const usedIds = new Set<string>(); - - const processNode = (n: Node) => { - if (n.kind !== "element") { - return; - } - - if (n.name === "section") { - const idAttr = n.attributes.get("id"); - if (!idAttr) { - return; - } - - let newId: string; - if (sectionStack.length === 0) { - newId = `section--${idAttr}`; - } else { - newId = `section--${sectionStack.join("--")}--${idAttr}`; - } - - if (usedIds.has(newId)) { - throw new NuldocError( - `[nuldoc.tohtml] Duplicate section ID: ${newId}`, - ); - } - - usedIds.add(newId); - n.attributes.set("id", newId); - sectionStack.push(idAttr); - - forEachChild(n, processNode); - - sectionStack.pop(); - } else { - forEachChild(n, processNode); - } - }; - - forEachChild(doc.root, processNode); -} - -function setSectionTitleAnchor(doc: Document) { - const sectionStack: Element[] = []; - const g = (c: Node) => { - if (c.kind !== "element") { - return; - } - - if (c.name === "section") { - sectionStack.push(c); - } - forEachChild(c, g); - if (c.name === "section") { - sectionStack.pop(); - } - if (c.name === "h") { - const currentSection = sectionStack[sectionStack.length - 1]; - if (!currentSection) { - throw new NuldocError( - "[nuldoc.tohtml] <h> element must be inside <section>", - ); - } - const sectionId = currentSection.attributes.get("id"); - const aElement: Element = { - kind: "element", - name: "a", - attributes: new Map(), - children: c.children, - }; - aElement.attributes.set("href", `#${sectionId}`); - c.children = [aElement]; - } - }; - forEachChild(doc.root, g); -} - -function transformSectionTitleElement(doc: Document) { - let sectionLevel = 1; - const g = (c: Node) => { - if (c.kind !== "element") { - return; - } - - if (c.name === "section") { - sectionLevel += 1; - c.attributes.set("--section-level", sectionLevel.toString()); - } - forEachChild(c, g); - if (c.name === "section") { - sectionLevel -= 1; - } - if (c.name === "h") { - c.name = `h${sectionLevel}`; - } - }; - forEachChild(doc.root, g); -} - -function transformNoteElement(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element" || n.name !== "note") { - return; - } - - const labelElement: Element = { - kind: "element", - name: "div", - attributes: new Map([["class", "admonition-label"]]), - children: [{ - kind: "text", - content: "NOTE", - raw: false, - }], - }; - const contentElement: Element = { - kind: "element", - name: "div", - attributes: new Map([["class", "admonition-content"]]), - children: n.children, - }; - n.name = "div"; - addClass(n, "admonition"); - n.children = [ - labelElement, - contentElement, - ]; - }); -} - -function addAttributesToExternalLinkElement(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element" || n.name !== "a") { - return; - } - - const href = n.attributes.get("href") ?? ""; - if (!href.startsWith("http")) { - return; - } - n.attributes - .set("target", "_blank") - .set("rel", "noreferrer"); - }); -} - -function setDefaultLangAttribute(_doc: Document) { - // TODO - // if (!e.attributes.has("lang")) { - // e.attributes.set("lang", "ja-JP"); - // } -} - -function traverseFootnotes(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element" || n.name !== "footnote") { - return; - } - - // TODO - // <footnote>x</footnote> - // - // <sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1">1</a>]</sup> - // - // <div class="footnote" id="_footnotedef_1"> - // <a href="#_footnoteref_1">1</a>. RAS syndrome - // </div> - n.name = "span"; - n.children = []; - }); -} - -async function transformAndHighlightCodeBlockElement(doc: Document) { - await forEachChildRecursivelyAsync(doc.root, async (n) => { - if (n.kind !== "element" || n.name !== "codeblock") { - return; - } - - const language = n.attributes.get("language") || "text"; - const sourceCodeNode = n.children[0] as Text | RawHTML; - const sourceCode = sourceCodeNode.content; - - const highlighted = await codeToHtml(sourceCode, { - lang: language in bundledLanguages ? language as BundledLanguage : "text", - theme: "github-light", - colorReplacements: { - "#fff": "#f5f5f5", - }, - }); - - sourceCodeNode.content = highlighted; - sourceCodeNode.raw = true; - n.name = "div"; - n.attributes.set("class", "codeblock"); - }); -} |
