summaryrefslogtreecommitdiffhomepage
path: root/vhosts/blog/nuldoc-src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-06-27 23:39:31 +0900
committernsfisis <nsfisis@gmail.com>2025-06-27 23:39:31 +0900
commit674fe965550444db87edc7937ff6932e1a918d9d (patch)
treee8a80dd958d3e082485286bf5785a7992b6e6b0e /vhosts/blog/nuldoc-src
parentfe4d1d625b53796c5f20399790e5ff8c7a7e1608 (diff)
downloadnsfisis.dev-674fe965550444db87edc7937ff6932e1a918d9d.tar.gz
nsfisis.dev-674fe965550444db87edc7937ff6932e1a918d9d.tar.zst
nsfisis.dev-674fe965550444db87edc7937ff6932e1a918d9d.zip
feat(meta): rename vhosts/ directory to services/
Diffstat (limited to 'vhosts/blog/nuldoc-src')
-rw-r--r--vhosts/blog/nuldoc-src/commands/build.ts260
-rw-r--r--vhosts/blog/nuldoc-src/commands/new.ts97
-rw-r--r--vhosts/blog/nuldoc-src/commands/serve.ts45
-rw-r--r--vhosts/blog/nuldoc-src/components/GlobalFooter.tsx9
-rw-r--r--vhosts/blog/nuldoc-src/components/GlobalHeader.tsx27
-rw-r--r--vhosts/blog/nuldoc-src/components/PageLayout.tsx61
-rw-r--r--vhosts/blog/nuldoc-src/components/Pagination.tsx45
-rw-r--r--vhosts/blog/nuldoc-src/components/PostPageEntry.tsx39
-rw-r--r--vhosts/blog/nuldoc-src/components/SlidePageEntry.tsx39
-rw-r--r--vhosts/blog/nuldoc-src/components/StaticScript.tsx18
-rw-r--r--vhosts/blog/nuldoc-src/components/StaticStylesheet.tsx11
-rw-r--r--vhosts/blog/nuldoc-src/components/utils.ts8
-rw-r--r--vhosts/blog/nuldoc-src/config.ts41
-rw-r--r--vhosts/blog/nuldoc-src/djot/djot2ndoc.ts842
-rw-r--r--vhosts/blog/nuldoc-src/djot/document.ts60
-rw-r--r--vhosts/blog/nuldoc-src/djot/parse.ts33
-rw-r--r--vhosts/blog/nuldoc-src/djot/to_html.ts449
-rw-r--r--vhosts/blog/nuldoc-src/dom.ts102
-rw-r--r--vhosts/blog/nuldoc-src/errors.ts17
-rw-r--r--vhosts/blog/nuldoc-src/generators/about.ts23
-rw-r--r--vhosts/blog/nuldoc-src/generators/atom.ts79
-rw-r--r--vhosts/blog/nuldoc-src/generators/home.ts19
-rw-r--r--vhosts/blog/nuldoc-src/generators/not_found.ts21
-rw-r--r--vhosts/blog/nuldoc-src/generators/post.ts63
-rw-r--r--vhosts/blog/nuldoc-src/generators/post_list.ts63
-rw-r--r--vhosts/blog/nuldoc-src/generators/slide.ts53
-rw-r--r--vhosts/blog/nuldoc-src/generators/slide_list.ts23
-rw-r--r--vhosts/blog/nuldoc-src/generators/tag.ts33
-rw-r--r--vhosts/blog/nuldoc-src/generators/tag_list.ts23
-rw-r--r--vhosts/blog/nuldoc-src/generators/tagged_page.ts4
-rw-r--r--vhosts/blog/nuldoc-src/jsx/jsx-runtime.ts27
-rw-r--r--vhosts/blog/nuldoc-src/jsx/render.ts45
-rw-r--r--vhosts/blog/nuldoc-src/jsx/types.d.ts83
-rw-r--r--vhosts/blog/nuldoc-src/main.ts19
-rw-r--r--vhosts/blog/nuldoc-src/page.ts9
-rw-r--r--vhosts/blog/nuldoc-src/pages/AboutPage.tsx110
-rw-r--r--vhosts/blog/nuldoc-src/pages/AtomPage.tsx26
-rw-r--r--vhosts/blog/nuldoc-src/pages/HomePage.tsx53
-rw-r--r--vhosts/blog/nuldoc-src/pages/NotFoundPage.tsx27
-rw-r--r--vhosts/blog/nuldoc-src/pages/PostListPage.tsx54
-rw-r--r--vhosts/blog/nuldoc-src/pages/PostPage.tsx66
-rw-r--r--vhosts/blog/nuldoc-src/pages/SlideListPage.tsx42
-rw-r--r--vhosts/blog/nuldoc-src/pages/SlidePage.tsx72
-rw-r--r--vhosts/blog/nuldoc-src/pages/TagListPage.tsx57
-rw-r--r--vhosts/blog/nuldoc-src/pages/TagPage.tsx43
-rw-r--r--vhosts/blog/nuldoc-src/render.ts13
-rw-r--r--vhosts/blog/nuldoc-src/renderers/html.ts310
-rw-r--r--vhosts/blog/nuldoc-src/renderers/xml.ts130
-rw-r--r--vhosts/blog/nuldoc-src/revision.ts37
-rw-r--r--vhosts/blog/nuldoc-src/slide/parse.ts20
-rw-r--r--vhosts/blog/nuldoc-src/slide/slide.ts67
51 files changed, 0 insertions, 3917 deletions
diff --git a/vhosts/blog/nuldoc-src/commands/build.ts b/vhosts/blog/nuldoc-src/commands/build.ts
deleted file mode 100644
index 3f765441..00000000
--- a/vhosts/blog/nuldoc-src/commands/build.ts
+++ /dev/null
@@ -1,260 +0,0 @@
-import { dirname, join, joinGlobs } from "@std/path";
-import { ensureDir, expandGlob } from "@std/fs";
-import { generateFeedPageFromEntries } from "../generators/atom.ts";
-import { Config, getTagLabel } from "../config.ts";
-import { parseDjotFile } from "../djot/parse.ts";
-import { Page } from "../page.ts";
-import { render } from "../render.ts";
-import { dateToString } from "../revision.ts";
-import { generateAboutPage } from "../generators/about.ts";
-import { generateHomePage } from "../generators/home.ts";
-import { generateNotFoundPage } from "../generators/not_found.ts";
-import {
- generatePostPage,
- getPostPublishedDate,
- PostPage,
-} from "../generators/post.ts";
-import { generatePostListPages } from "../generators/post_list.ts";
-import { generateSlidePage, SlidePage } from "../generators/slide.ts";
-import { generateSlideListPage } from "../generators/slide_list.ts";
-import { generateTagPage, TagPage } from "../generators/tag.ts";
-import { TaggedPage } from "../generators/tagged_page.ts";
-import { generateTagListPage } from "../generators/tag_list.ts";
-import { parseSlideFile } from "../slide/parse.ts";
-
-export async function runBuildCommand(config: Config) {
- const posts = await buildPostPages(config);
- await buildPostListPage(posts, config);
- const slides = await buildSlidePages(config);
- await buildSlideListPage(slides, config);
- const tags = await buildTagPages(posts, slides, config);
- await buildTagListPage(tags, config);
- await buildHomePage(config);
- await buildAboutPage(slides, config);
- await buildNotFoundPage(config);
- await buildFeedOfAllContents(posts, slides, config);
- await copyStaticFiles(config);
- await copyAssetFiles(slides, config);
-}
-
-async function buildPostPages(config: Config): Promise<PostPage[]> {
- const sourceDir = join(Deno.cwd(), config.locations.contentDir, "posts");
- const postFiles = await collectPostFiles(sourceDir);
- const posts = await parsePosts(postFiles, config);
- for (const post of posts) {
- await writePage(post, config);
- }
- return posts;
-}
-
-async function collectPostFiles(sourceDir: string): Promise<string[]> {
- const filePaths = [];
- const globPattern = joinGlobs([sourceDir, "**", "*.dj"]);
- for await (const entry of expandGlob(globPattern)) {
- filePaths.push(entry.path);
- }
- return filePaths;
-}
-
-async function parsePosts(
- postFiles: string[],
- config: Config,
-): Promise<PostPage[]> {
- const posts = [];
- for (const postFile of postFiles) {
- posts.push(
- await generatePostPage(await parseDjotFile(postFile, config), config),
- );
- }
- return posts;
-}
-
-async function buildPostListPage(posts: PostPage[], config: Config) {
- // Sort posts by published date (newest first)
- const sortedPosts = [...posts].sort((a, b) => {
- const ta = dateToString(getPostPublishedDate(a));
- const tb = dateToString(getPostPublishedDate(b));
- if (ta > tb) return -1;
- if (ta < tb) return 1;
- return 0;
- });
-
- const postListPages = await generatePostListPages(sortedPosts, config);
- for (const page of postListPages) {
- await writePage(page, config);
- }
-
- const postFeedPage = await generateFeedPageFromEntries(
- "/posts/",
- "posts",
- `投稿一覧|${config.blog.siteName}`,
- posts,
- config,
- );
- await writePage(postFeedPage, config);
-}
-
-async function buildSlidePages(config: Config): Promise<SlidePage[]> {
- const sourceDir = join(Deno.cwd(), config.locations.contentDir, "slides");
- const slideFiles = await collectSlideFiles(sourceDir);
- const slides = await parseSlides(slideFiles, config);
- for (const slide of slides) {
- await writePage(slide, config);
- }
- return slides;
-}
-
-async function collectSlideFiles(sourceDir: string): Promise<string[]> {
- const filePaths = [];
- const globPattern = joinGlobs([sourceDir, "**", "*.toml"]);
- for await (const entry of expandGlob(globPattern)) {
- filePaths.push(entry.path);
- }
- return filePaths;
-}
-
-async function parseSlides(
- slideFiles: string[],
- config: Config,
-): Promise<SlidePage[]> {
- const slides = [];
- for (const slideFile of slideFiles) {
- slides.push(
- await generateSlidePage(await parseSlideFile(slideFile), config),
- );
- }
- return slides;
-}
-
-async function buildSlideListPage(slides: SlidePage[], config: Config) {
- const slideListPage = await generateSlideListPage(slides, config);
- await writePage(slideListPage, config);
- const slideFeedPage = await generateFeedPageFromEntries(
- slideListPage.href,
- "slides",
- `スライド一覧|${config.blog.siteName}`,
- slides,
- config,
- );
- await writePage(slideFeedPage, config);
-}
-
-async function buildHomePage(config: Config) {
- const homePage = await generateHomePage(config);
- await writePage(homePage, config);
-}
-
-async function buildAboutPage(slides: SlidePage[], config: Config) {
- const aboutPage = await generateAboutPage(slides, config);
- await writePage(aboutPage, config);
-}
-
-async function buildNotFoundPage(config: Config) {
- const notFoundPage = await generateNotFoundPage(config);
- await writePage(notFoundPage, config);
-}
-
-async function buildFeedOfAllContents(
- posts: PostPage[],
- slides: SlidePage[],
- config: Config,
-) {
- const feed = await generateFeedPageFromEntries(
- "/",
- "all",
- config.blog.siteName,
- [...posts, ...slides],
- config,
- );
- await writePage(feed, config);
-}
-
-async function buildTagPages(
- posts: PostPage[],
- slides: SlidePage[],
- config: Config,
-): Promise<TagPage[]> {
- const tagsAndPages = collectTags([...posts, ...slides]);
- const tags = [];
- for (const [tag, pages] of tagsAndPages) {
- const tagPage = await generateTagPage(tag, pages, config);
- await writePage(tagPage, config);
- const tagFeedPage = await generateFeedPageFromEntries(
- tagPage.href,
- `tag-${tag}`,
- `タグ「${getTagLabel(config, tag)}」一覧|${config.blog.siteName}`,
- pages,
- config,
- );
- await writePage(tagFeedPage, config);
- tags.push(tagPage);
- }
- return tags;
-}
-
-async function buildTagListPage(tags: TagPage[], config: Config) {
- const tagListPage = await generateTagListPage(tags, config);
- await writePage(tagListPage, config);
-}
-
-function collectTags(taggedPages: TaggedPage[]): [string, TaggedPage[]][] {
- const tagsAndPages = new Map();
- for (const page of taggedPages) {
- for (const tag of page.tags) {
- if (!tagsAndPages.has(tag)) {
- tagsAndPages.set(tag, []);
- }
- tagsAndPages.get(tag).push(page);
- }
- }
-
- const result: [string, TaggedPage[]][] = [];
- for (const tag of Array.from(tagsAndPages.keys()).sort()) {
- result.push([
- tag,
- tagsAndPages.get(tag).sort((a: TaggedPage, b: TaggedPage) => {
- const ta = dateToString(getPostPublishedDate(a));
- const tb = dateToString(getPostPublishedDate(b));
- if (ta > tb) return -1;
- if (ta < tb) return 1;
- return 0;
- }),
- ]);
- }
- return result;
-}
-
-async function copyStaticFiles(config: Config) {
- const globPattern = joinGlobs([Deno.cwd(), config.locations.staticDir, "*"]);
- for await (const entry of expandGlob(globPattern)) {
- const src = entry.path;
- const dst = src.replace(
- config.locations.staticDir,
- config.locations.destDir,
- );
- await Deno.copyFile(src, dst);
- }
-}
-
-async function copyAssetFiles(slides: SlidePage[], config: Config) {
- const cwd = Deno.cwd();
- const contentDir = join(cwd, config.locations.contentDir);
- const destDir = join(cwd, config.locations.destDir);
-
- for (const slide of slides) {
- const src = join(contentDir, slide.slideLink);
- const dst = join(destDir, slide.slideLink);
- await ensureDir(dirname(dst));
- await Deno.copyFile(src, dst);
- }
-}
-
-async function writePage(page: Page, config: Config) {
- const destFilePath = join(
- Deno.cwd(),
- config.locations.destDir,
- page.destFilePath,
- );
- await ensureDir(dirname(destFilePath));
- await Deno.writeTextFile(destFilePath, render(page.root, page.renderer));
-}
diff --git a/vhosts/blog/nuldoc-src/commands/new.ts b/vhosts/blog/nuldoc-src/commands/new.ts
deleted file mode 100644
index 651c59e6..00000000
--- a/vhosts/blog/nuldoc-src/commands/new.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import { dirname, join } from "@std/path";
-import { ensureDir } from "@std/fs";
-import { parseArgs } from "@std/cli";
-import { Config } from "../config.ts";
-
-export async function runNewCommand(config: Config) {
- const parsedArgs = parseArgs(Deno.args, {
- string: ["date"],
- });
-
- const type = parsedArgs._[1];
- if (type !== "post" && type !== "slide") {
- console.log(`Usage: nuldoc new <type>
-
-<type> must be either "post" or "slide".
-
-OPTIONS:
- --date <DATE>
-`);
- Deno.exit(1);
- }
-
- const ymd = (() => {
- if (parsedArgs.date) {
- return parsedArgs.date;
- }
-
- const now = new Date();
- const y = now.getFullYear();
- const d = (now.getMonth() + 1).toString().padStart(2, "0");
- const m = now.getDate().toString().padStart(2, "0");
- return `${y}-${d}-${m}`;
- })();
-
- const destFilePath = join(
- Deno.cwd(),
- config.locations.contentDir,
- getDirPath(type),
- ymd,
- getFilename(type),
- );
-
- await ensureDir(dirname(destFilePath));
- await Deno.writeTextFile(destFilePath, getTemplate(type, ymd));
- console.log(
- `New file ${
- destFilePath.replace(Deno.cwd(), "")
- } was successfully created.`,
- );
-}
-
-function getFilename(type: "post" | "slide"): string {
- return type === "post" ? "TODO.dj" : "TODO.toml";
-}
-
-function getDirPath(type: "post" | "slide"): string {
- return type === "post" ? "posts" : "slides";
-}
-
-function getTemplate(type: "post" | "slide", date: string): string {
- const uuid = crypto.randomUUID();
- if (type === "post") {
- return `---
-[article]
-uuid = "${uuid}"
-title = "TODO"
-description = "TODO"
-tags = [
- "TODO",
-]
-
-[[article.revisions]]
-date = "${date}"
-remark = "公開"
----
-{#TODO}
-# TODO
-
-TODO
-`;
- } else {
- return `[slide]
-uuid = "${uuid}"
-title = "TODO"
-event = "TODO"
-talkType = "TODO"
-link = "TODO"
-tags = [
- "TODO",
-]
-
-[[slide.revisions]]
-date = "${date}"
-remark = "登壇"
-`;
- }
-}
diff --git a/vhosts/blog/nuldoc-src/commands/serve.ts b/vhosts/blog/nuldoc-src/commands/serve.ts
deleted file mode 100644
index e944aaf0..00000000
--- a/vhosts/blog/nuldoc-src/commands/serve.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { serveDir, STATUS_CODE, STATUS_TEXT } from "@std/http";
-import { join } from "@std/path";
-import { Config } from "../config.ts";
-import { runBuildCommand } from "./build.ts";
-
-function isResourcePath(pathname: string): boolean {
- const EXTENSIONS = [
- ".css",
- ".gif",
- ".ico",
- ".jpeg",
- ".jpg",
- ".js",
- ".png",
- ".svg",
- ];
- return EXTENSIONS.some((ext) => pathname.endsWith(ext));
-}
-
-export function runServeCommand(config: Config) {
- const rootDir = join(Deno.cwd(), config.locations.destDir);
- Deno.serve({ hostname: "127.0.0.1" }, async (req) => {
- const pathname = new URL(req.url).pathname;
- if (!isResourcePath(pathname)) {
- await runBuildCommand(config);
- console.log("rebuild");
- }
- const res = await serveDir(req, {
- fsRoot: rootDir,
- showIndex: true,
- });
- if (res.status !== STATUS_CODE.NotFound) {
- return res;
- }
-
- const notFoundHtml = await Deno.readTextFile(join(rootDir, "404.html"));
- return new Response(notFoundHtml, {
- status: STATUS_CODE.NotFound,
- statusText: STATUS_TEXT[STATUS_CODE.NotFound],
- headers: {
- "content-type": "text/html",
- },
- });
- });
-}
diff --git a/vhosts/blog/nuldoc-src/components/GlobalFooter.tsx b/vhosts/blog/nuldoc-src/components/GlobalFooter.tsx
deleted file mode 100644
index 757beced..00000000
--- a/vhosts/blog/nuldoc-src/components/GlobalFooter.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Config } from "../config.ts";
-
-export default function GlobalFooter({ config }: { config: Config }) {
- return (
- <footer className="footer">
- {`&copy; ${config.blog.siteCopyrightYear} ${config.blog.author}`}
- </footer>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/components/GlobalHeader.tsx b/vhosts/blog/nuldoc-src/components/GlobalHeader.tsx
deleted file mode 100644
index c0fa7e8b..00000000
--- a/vhosts/blog/nuldoc-src/components/GlobalHeader.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Config } from "../config.ts";
-
-export default function GlobalHeader({ config }: { config: Config }) {
- return (
- <header className="header">
- <div className="site-logo">
- <a href="/">{config.blog.siteName}</a>
- </div>
- <nav className="nav">
- <ul>
- <li>
- <a href="/about/">About</a>
- </li>
- <li>
- <a href="/posts/">Posts</a>
- </li>
- <li>
- <a href="/slides/">Slides</a>
- </li>
- <li>
- <a href="/tags/">Tags</a>
- </li>
- </ul>
- </nav>
- </header>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/components/PageLayout.tsx b/vhosts/blog/nuldoc-src/components/PageLayout.tsx
deleted file mode 100644
index 1cd0aebf..00000000
--- a/vhosts/blog/nuldoc-src/components/PageLayout.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { Config } from "../config.ts";
-import { JSXNode } from "myjsx/jsx-runtime";
-import StaticStylesheet from "./StaticStylesheet.tsx";
-
-type Props = {
- metaCopyrightYear: number;
- metaDescription: string;
- metaKeywords?: string[];
- metaTitle: string;
- metaAtomFeedHref?: string;
- requiresSyntaxHighlight?: boolean;
- config: Config;
- children: JSXNode;
-};
-
-export default function PageLayout(
- {
- metaCopyrightYear,
- metaDescription,
- metaKeywords,
- metaTitle,
- metaAtomFeedHref,
- requiresSyntaxHighlight: _,
- config,
- children,
- }: Props,
-) {
- return (
- <html lang="ja-JP">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <meta name="author" content={config.blog.author} />
- <meta
- name="copyright"
- content={`&copy; ${metaCopyrightYear} ${config.blog.author}`}
- />
- <meta name="description" content={metaDescription} />
- {metaKeywords && metaKeywords.length !== 0 &&
- <meta name="keywords" content={metaKeywords.join(",")} />}
- <meta property="og:type" content="article" />
- <meta property="og:title" content={metaTitle} />
- <meta property="og:description" content={metaDescription} />
- <meta property="og:site_name" content={config.blog.siteName} />
- <meta property="og:locale" content="ja_JP" />
- {metaAtomFeedHref &&
- (
- <link
- rel="alternate"
- href={metaAtomFeedHref}
- type="application/atom+xml"
- />
- )}
- <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
- <title>{metaTitle}</title>
- <StaticStylesheet fileName="/style.css" config={config} />
- </head>
- {children}
- </html>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/components/Pagination.tsx b/vhosts/blog/nuldoc-src/components/Pagination.tsx
deleted file mode 100644
index 5527c924..00000000
--- a/vhosts/blog/nuldoc-src/components/Pagination.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-type Props = {
- currentPage: number;
- totalPages: number;
- basePath: string;
-};
-
-export default function Pagination(
- { currentPage, totalPages, basePath }: Props,
-) {
- if (totalPages <= 1) {
- return <div></div>;
- }
-
- const prevPage = currentPage > 1 ? currentPage - 1 : null;
- const nextPage = currentPage < totalPages ? currentPage + 1 : null;
-
- const prevHref = prevPage === 1 ? basePath : `${basePath}${prevPage}/`;
- const nextHref = `${basePath}${nextPage}/`;
-
- return (
- <nav className="pagination">
- <div className="pagination-prev">
- {prevPage
- ? (
- <a href={prevHref}>
- 前のページ
- </a>
- )
- : null}
- </div>
- <div className="pagination-info">
- {String(currentPage)} / {String(totalPages)}
- </div>
- <div className="pagination-next">
- {nextPage
- ? (
- <a href={nextHref}>
- 次のページ
- </a>
- )
- : null}
- </div>
- </nav>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/components/PostPageEntry.tsx b/vhosts/blog/nuldoc-src/components/PostPageEntry.tsx
deleted file mode 100644
index 2708b009..00000000
--- a/vhosts/blog/nuldoc-src/components/PostPageEntry.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import {
- getPostPublishedDate,
- getPostUpdatedDate,
- postHasAnyUpdates,
- PostPage,
-} from "../generators/post.ts";
-import { dateToString } from "../revision.ts";
-
-export default function PostPageEntry({ post }: { post: PostPage }) {
- return (
- <article className="post-entry">
- <a href={post.href}>
- <header className="entry-header">
- <h2>{post.title}</h2>
- </header>
- <section className="entry-content">
- <p>{post.description}</p>
- </section>
- <footer className="entry-footer">
- <time datetime={dateToString(getPostPublishedDate(post))}>
- {dateToString(getPostPublishedDate(post))}
- </time>
- {" 投稿"}
- {
- // TODO(jsx): support Fragment and merge them.
- postHasAnyUpdates(post) && "、"
- }
- {postHasAnyUpdates(post) &&
- (
- <time datetime={dateToString(getPostUpdatedDate(post))}>
- {dateToString(getPostUpdatedDate(post))}
- </time>
- )}
- {postHasAnyUpdates(post) && " 更新"}
- </footer>
- </a>
- </article>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/components/SlidePageEntry.tsx b/vhosts/blog/nuldoc-src/components/SlidePageEntry.tsx
deleted file mode 100644
index d2cf9a17..00000000
--- a/vhosts/blog/nuldoc-src/components/SlidePageEntry.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import {
- getPostPublishedDate,
- getPostUpdatedDate,
- postHasAnyUpdates,
-} from "../generators/post.ts";
-import { SlidePage } from "../generators/slide.ts";
-import { dateToString } from "../revision.ts";
-
-export default function SlidePageEntry({ slide }: { slide: SlidePage }) {
- return (
- <article className="post-entry">
- <a href={slide.href}>
- <header className="entry-header">
- <h2>{slide.description}</h2>
- </header>
- <section className="entry-content">
- <p>{slide.title}</p>
- </section>
- <footer className="entry-footer">
- <time datetime={dateToString(getPostPublishedDate(slide))}>
- {dateToString(getPostPublishedDate(slide))}
- </time>
- {" 登壇"}
- {
- // TODO(jsx): support Fragment and merge them.
- postHasAnyUpdates(slide) && "、"
- }
- {postHasAnyUpdates(slide) &&
- (
- <time datetime={dateToString(getPostUpdatedDate(slide))}>
- {dateToString(getPostUpdatedDate(slide))}
- </time>
- )}
- {postHasAnyUpdates(slide) && " 更新"}
- </footer>
- </a>
- </article>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/components/StaticScript.tsx b/vhosts/blog/nuldoc-src/components/StaticScript.tsx
deleted file mode 100644
index 0e3ab194..00000000
--- a/vhosts/blog/nuldoc-src/components/StaticScript.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { join } from "@std/path";
-import { Config } from "../config.ts";
-import { calculateFileHash } from "./utils.ts";
-
-export default async function StaticScript(
- { fileName, type, defer, config }: {
- fileName: string;
- type?: string;
- defer?: "true";
- config: Config;
- },
-) {
- const filePath = join(Deno.cwd(), config.locations.staticDir, fileName);
- const hash = await calculateFileHash(filePath);
- return (
- <script src={`${fileName}?h=${hash}`} type={type} defer={defer}></script>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/components/StaticStylesheet.tsx b/vhosts/blog/nuldoc-src/components/StaticStylesheet.tsx
deleted file mode 100644
index 52b695e5..00000000
--- a/vhosts/blog/nuldoc-src/components/StaticStylesheet.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { join } from "@std/path";
-import { Config } from "../config.ts";
-import { calculateFileHash } from "./utils.ts";
-
-export default async function StaticStylesheet(
- { fileName, config }: { fileName: string; config: Config },
-) {
- const filePath = join(Deno.cwd(), config.locations.staticDir, fileName);
- const hash = await calculateFileHash(filePath);
- return <link rel="stylesheet" href={`${fileName}?h=${hash}`} />;
-}
diff --git a/vhosts/blog/nuldoc-src/components/utils.ts b/vhosts/blog/nuldoc-src/components/utils.ts
deleted file mode 100644
index 14059b5b..00000000
--- a/vhosts/blog/nuldoc-src/components/utils.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Hash } from "checksum/mod.ts";
-
-export async function calculateFileHash(
- filePath: string,
-): Promise<string> {
- const content = await Deno.readFile(filePath);
- return new Hash("md5").digest(content).hex();
-}
diff --git a/vhosts/blog/nuldoc-src/config.ts b/vhosts/blog/nuldoc-src/config.ts
deleted file mode 100644
index adcb5632..00000000
--- a/vhosts/blog/nuldoc-src/config.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { join } from "@std/path";
-import { parse as parseToml } from "@std/toml";
-import { z } from "zod/mod.ts";
-
-const ConfigSchema = z.object({
- locations: z.object({
- contentDir: z.string(),
- destDir: z.string(),
- staticDir: z.string(),
- }),
- rendering: z.object({
- html: z.object({
- indentWidth: z.number(),
- }),
- }),
- blog: z.object({
- author: z.string(),
- fqdn: z.string(),
- siteName: z.string(),
- siteCopyrightYear: z.number(),
- postsPerPage: z.number().default(10),
- tagLabels: z.record(z.string(), z.string()),
- }),
-});
-
-export type Config = z.infer<typeof ConfigSchema>;
-
-export function getTagLabel(c: Config, slug: string): string {
- if (!(slug in c.blog.tagLabels)) {
- throw new Error(`Unknown tag: ${slug}`);
- }
- return c.blog.tagLabels[slug];
-}
-
-export function getDefaultConfigPath(): string {
- return join(Deno.cwd(), "nuldoc.toml");
-}
-
-export async function loadConfig(filePath: string): Promise<Config> {
- return ConfigSchema.parse(parseToml(await Deno.readTextFile(filePath)));
-}
diff --git a/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts b/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts
deleted file mode 100644
index 90b1289c..00000000
--- a/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts
+++ /dev/null
@@ -1,842 +0,0 @@
-import {
- Block as DjotBlock,
- BlockQuote as DjotBlockQuote,
- BulletList as DjotBulletList,
- CodeBlock as DjotCodeBlock,
- Definition as DjotDefinition,
- DefinitionList as DjotDefinitionList,
- DefinitionListItem as DjotDefinitionListItem,
- Delete as DjotDelete,
- DisplayMath as DjotDisplayMath,
- Div as DjotDiv,
- Doc as DjotDoc,
- DoubleQuoted as DjotDoubleQuoted,
- Email as DjotEmail,
- Emph as DjotEmph,
- FootnoteReference as DjotFootnoteReference,
- HardBreak as DjotHardBreak,
- Heading as DjotHeading,
- Image as DjotImage,
- Inline as DjotInline,
- InlineMath as DjotInlineMath,
- Insert as DjotInsert,
- Link as DjotLink,
- ListItem as DjotListItem,
- Mark as DjotMark,
- NonBreakingSpace as DjotNonBreakingSpace,
- OrderedList as DjotOrderedList,
- Para as DjotPara,
- RawBlock as DjotRawBlock,
- RawInline as DjotRawInline,
- Section as DjotSection,
- SingleQuoted as DjotSingleQuoted,
- SmartPunctuation as DjotSmartPunctuation,
- SoftBreak as DjotSoftBreak,
- Span as DjotSpan,
- Str as DjotStr,
- Strong as DjotStrong,
- Subscript as DjotSubscript,
- Superscript as DjotSuperscript,
- Symb as DjotSymb,
- Table as DjotTable,
- TaskList as DjotTaskList,
- TaskListItem as DjotTaskListItem,
- Term as DjotTerm,
- ThematicBreak as DjotThematicBreak,
- Url as DjotUrl,
- Verbatim as DjotVerbatim,
-} from "@djot/djot";
-import { Element, Node } from "../dom.ts";
-
-function processBlock(node: DjotBlock): Element {
- switch (node.tag) {
- case "section":
- return processSection(node);
- case "para":
- return processPara(node);
- case "heading":
- return processHeading(node);
- case "thematic_break":
- return processThematicBreak(node);
- case "block_quote":
- return processBlockQuote(node);
- case "code_block":
- return processCodeBlock(node);
- case "bullet_list":
- return processBulletList(node);
- case "ordered_list":
- return processOrderedList(node);
- case "task_list":
- return processTaskList(node);
- case "definition_list":
- return processDefinitionList(node);
- case "table":
- return processTable(node);
- case "div":
- return processDiv(node);
- case "raw_block":
- return processRawBlock(node);
- }
-}
-
-function processSection(node: DjotSection): Element {
- return {
- kind: "element",
- name: "section",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processBlock),
- };
-}
-
-function processPara(node: DjotPara): Element {
- return {
- kind: "element",
- name: "p",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processInline),
- };
-}
-
-function processHeading(node: DjotHeading): Element {
- const attributes = convertAttributes(node.attributes);
- return {
- kind: "element",
- name: "h",
- attributes,
- children: node.children.map(processInline),
- };
-}
-
-function processThematicBreak(node: DjotThematicBreak): Element {
- return {
- kind: "element",
- name: "hr",
- attributes: convertAttributes(node.attributes),
- children: [],
- };
-}
-
-function processBlockQuote(node: DjotBlockQuote): Element {
- return {
- kind: "element",
- name: "blockquote",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processBlock),
- };
-}
-
-function processCodeBlock(node: DjotCodeBlock): Element {
- const attributes = convertAttributes(node.attributes);
- if (node.lang) {
- attributes.set("language", node.lang);
- }
- if (node.attributes?.filename) {
- attributes.set("filename", node.attributes.filename);
- }
- if (node.attributes?.numbered) {
- attributes.set("numbered", "true");
- }
- return {
- kind: "element",
- name: "codeblock",
- attributes,
- children: [
- {
- kind: "text",
- content: node.text,
- raw: false,
- },
- ],
- };
-}
-
-function processBulletList(node: DjotBulletList): Element {
- const attributes = convertAttributes(node.attributes);
- attributes.set("--tight", node.tight ? "true" : "false");
- return {
- kind: "element",
- name: "ul",
- attributes,
- children: node.children.map(processListItem),
- };
-}
-
-function processOrderedList(node: DjotOrderedList): Element {
- const attributes = convertAttributes(node.attributes);
- attributes.set("--tight", node.tight ? "true" : "false");
- if (node.start !== undefined && node.start !== 1) {
- attributes.set("start", node.start.toString());
- }
- return {
- kind: "element",
- name: "ol",
- attributes,
- children: node.children.map(processListItem),
- };
-}
-
-function processTaskList(node: DjotTaskList): Element {
- const attributes = convertAttributes(node.attributes);
- attributes.set("type", "task");
- attributes.set("--tight", node.tight ? "true" : "false");
- return {
- kind: "element",
- name: "ul",
- attributes,
- children: node.children.map(processTaskListItem),
- };
-}
-
-function processListItem(node: DjotListItem): Element {
- return {
- kind: "element",
- name: "li",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processBlock),
- };
-}
-
-function processTaskListItem(node: DjotTaskListItem): Element {
- const attributes = convertAttributes(node.attributes);
- attributes.set("checked", node.checkbox === "checked" ? "true" : "false");
- return {
- kind: "element",
- name: "li",
- attributes,
- children: node.children.map(processBlock),
- };
-}
-
-function processDefinitionList(node: DjotDefinitionList): Element {
- return {
- kind: "element",
- name: "dl",
- attributes: convertAttributes(node.attributes),
- children: node.children.flatMap(processDefinitionListItem),
- };
-}
-
-function processDefinitionListItem(node: DjotDefinitionListItem): Element[] {
- return [
- processTerm(node.children[0]),
- processDefinition(node.children[1]),
- ];
-}
-
-function processTerm(node: DjotTerm): Element {
- return {
- kind: "element",
- name: "dt",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processInline),
- };
-}
-
-function processDefinition(node: DjotDefinition): Element {
- return {
- kind: "element",
- name: "dd",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processBlock),
- };
-}
-
-function processTable(node: DjotTable): Element {
- // Tables in Djot have a caption as first child and then rows
- // For now, we'll create a basic table structure and ignore caption
- const tableElement: Element = {
- kind: "element",
- name: "table",
- attributes: convertAttributes(node.attributes),
- children: [],
- };
-
- // Process caption if it exists (first child)
- if (node.children.length > 0 && node.children[0].tag === "caption") {
- const caption: Element = {
- kind: "element",
- name: "caption",
- attributes: new Map(),
- children: node.children[0].children.map(processInline),
- };
- tableElement.children.push(caption);
- }
-
- // Group rows into thead, tbody based on head property
- const headerRows: Element[] = [];
- const bodyRows: Element[] = [];
-
- // Start from index 1 to skip caption
- for (let i = 1; i < node.children.length; i++) {
- const row = node.children[i];
- if (row.tag === "row") {
- const rowElement: Element = {
- kind: "element",
- name: "tr",
- attributes: convertAttributes(row.attributes),
- children: row.children.map((cell) => {
- const cellElement: Element = {
- kind: "element",
- name: cell.head ? "th" : "td",
- attributes: convertAttributes(cell.attributes),
- children: cell.children.map(processInline),
- };
-
- // Set alignment attribute if needed
- if (cell.align !== "default") {
- cellElement.attributes.set("align", cell.align);
- }
-
- return cellElement;
- }),
- };
-
- if (row.head) {
- headerRows.push(rowElement);
- } else {
- bodyRows.push(rowElement);
- }
- }
- }
-
- // Add thead and tbody if needed
- if (headerRows.length > 0) {
- tableElement.children.push({
- kind: "element",
- name: "thead",
- attributes: new Map(),
- children: headerRows,
- });
- }
-
- if (bodyRows.length > 0) {
- tableElement.children.push({
- kind: "element",
- name: "tbody",
- attributes: new Map(),
- children: bodyRows,
- });
- }
-
- return tableElement;
-}
-
-function processInline(node: DjotInline): Node {
- switch (node.tag) {
- case "str":
- return processStr(node);
- case "soft_break":
- return processSoftBreak(node);
- case "hard_break":
- return processHardBreak(node);
- case "verbatim":
- return processVerbatim(node);
- case "emph":
- return processEmph(node);
- case "strong":
- return processStrong(node);
- case "link":
- return processLink(node);
- case "image":
- return processImage(node);
- case "mark":
- return processMark(node);
- case "superscript":
- return processSuperscript(node);
- case "subscript":
- return processSubscript(node);
- case "insert":
- return processInsert(node);
- case "delete":
- return processDelete(node);
- case "email":
- return processEmail(node);
- case "footnote_reference":
- return processFootnoteReference(node);
- case "url":
- return processUrl(node);
- case "span":
- return processSpan(node);
- case "inline_math":
- return processInlineMath(node);
- case "display_math":
- return processDisplayMath(node);
- case "non_breaking_space":
- return processNonBreakingSpace(node);
- case "symb":
- return processSymb(node);
- case "raw_inline":
- return processRawInline(node);
- case "double_quoted":
- return processDoubleQuoted(node);
- case "single_quoted":
- return processSingleQuoted(node);
- case "smart_punctuation":
- return processSmartPunctuation(node);
- }
-}
-
-function processStr(node: DjotStr): Node {
- return {
- kind: "text",
- content: node.text,
- raw: false,
- };
-}
-
-function processSoftBreak(_node: DjotSoftBreak): Node {
- return {
- kind: "text",
- content: "\n",
- raw: false,
- };
-}
-
-function processHardBreak(_node: DjotHardBreak): Node {
- return {
- kind: "element",
- name: "br",
- attributes: new Map(),
- children: [],
- };
-}
-
-function processVerbatim(node: DjotVerbatim): Element {
- return {
- kind: "element",
- name: "code",
- attributes: convertAttributes(node.attributes),
- children: [
- {
- kind: "text",
- content: node.text,
- raw: false,
- },
- ],
- };
-}
-
-function processEmph(node: DjotEmph): Element {
- return {
- kind: "element",
- name: "em",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processInline),
- };
-}
-
-function processStrong(node: DjotStrong): Element {
- return {
- kind: "element",
- name: "strong",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processInline),
- };
-}
-
-function processLink(node: DjotLink): Element {
- const attributes = convertAttributes(node.attributes);
- if (node.destination !== undefined) {
- attributes.set("href", node.destination);
- }
- return {
- kind: "element",
- name: "a",
- attributes,
- children: node.children.map(processInline),
- };
-}
-
-function processImage(node: DjotImage): Element {
- const attributes = convertAttributes(node.attributes);
- if (node.destination !== undefined) {
- attributes.set("src", node.destination);
- }
-
- // Alt text is derived from children in Djot
- const alt = node.children
- .map((child) => {
- if (child.tag === "str") {
- return child.text;
- }
- return "";
- })
- .join("");
-
- if (alt) {
- attributes.set("alt", alt);
- }
-
- return {
- kind: "element",
- name: "img",
- attributes,
- children: [],
- };
-}
-
-function processMark(node: DjotMark): Element {
- return {
- kind: "element",
- name: "mark",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processInline),
- };
-}
-
-function processSuperscript(node: DjotSuperscript): Element {
- return {
- kind: "element",
- name: "sup",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processInline),
- };
-}
-
-function processSubscript(node: DjotSubscript): Element {
- return {
- kind: "element",
- name: "sub",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processInline),
- };
-}
-
-function processInsert(node: DjotInsert): Element {
- return {
- kind: "element",
- name: "ins",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processInline),
- };
-}
-
-function processDelete(node: DjotDelete): Element {
- return {
- kind: "element",
- name: "del",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processInline),
- };
-}
-
-function processEmail(node: DjotEmail): Element {
- return {
- kind: "element",
- name: "email",
- attributes: convertAttributes(node.attributes),
- children: [
- {
- kind: "text",
- content: node.text,
- raw: false,
- },
- ],
- };
-}
-
-function processFootnoteReference(node: DjotFootnoteReference): Element {
- return {
- kind: "element",
- name: "footnoteref",
- attributes: new Map([["reference", node.text]]),
- children: [],
- };
-}
-
-function processUrl(node: DjotUrl): Element {
- return {
- kind: "element",
- name: "a",
- attributes: new Map([
- ["href", node.text],
- ...Object.entries(node.attributes || {}),
- ]),
- children: [
- {
- kind: "text",
- content: node.text,
- raw: false,
- },
- ],
- };
-}
-
-function processSpan(node: DjotSpan): Element {
- return {
- kind: "element",
- name: "span",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processInline),
- };
-}
-
-function processInlineMath(node: DjotInlineMath): Element {
- // For inline math, we'll wrap it in a span with a class
- return {
- kind: "element",
- name: "span",
- attributes: new Map([
- ["class", "math inline"],
- ...Object.entries(node.attributes || {}),
- ]),
- children: [
- {
- kind: "text",
- content: node.text,
- raw: false,
- },
- ],
- };
-}
-
-function processDisplayMath(node: DjotDisplayMath): Element {
- // For display math, we'll wrap it in a div with a class
- return {
- kind: "element",
- name: "div",
- attributes: new Map([
- ["class", "math display"],
- ...Object.entries(node.attributes || {}),
- ]),
- children: [
- {
- kind: "text",
- content: node.text,
- raw: false,
- },
- ],
- };
-}
-
-function processNonBreakingSpace(_node: DjotNonBreakingSpace): Node {
- return {
- kind: "text",
- content: "\u00A0", // Unicode non-breaking space
- raw: false,
- };
-}
-
-function processSymb(node: DjotSymb): Node {
- // Map symbol aliases to their Unicode characters
- const symbolMap: Record<string, string> = {
- "->": "→",
- "<-": "←",
- "<->": "↔",
- "=>": "⇒",
- "<=": "⇐",
- "<=>": "⇔",
- "--": "–", // en dash
- "---": "—", // em dash
- "...": "…", // ellipsis
- // Add more symbol mappings as needed
- };
-
- const symbolText = symbolMap[node.alias] || node.alias;
-
- return {
- kind: "text",
- content: symbolText,
- raw: false,
- };
-}
-
-function processRawInline(node: DjotRawInline): Node {
- // If the format is HTML, return as raw HTML
- if (node.format === "html" || node.format === "HTML") {
- return {
- kind: "text",
- content: node.text,
- raw: true,
- };
- }
-
- // For other formats, just return as text
- return {
- kind: "text",
- content: node.text,
- raw: false,
- };
-}
-
-function processDoubleQuoted(node: DjotDoubleQuoted): Node {
- const children = node.children.map(processInline);
- const attributes = convertAttributes(node.attributes);
-
- if (
- children.length === 1 && children[0].kind === "text" &&
- attributes.size === 0
- ) {
- const content = children[0].content;
- return {
- kind: "text",
- content: `\u201C${content}\u201D`,
- raw: false,
- };
- } else {
- return {
- kind: "element",
- name: "span",
- attributes: convertAttributes(node.attributes),
- children,
- };
- }
-}
-
-function processSingleQuoted(node: DjotSingleQuoted): Node {
- const children = node.children.map(processInline);
- const attributes = convertAttributes(node.attributes);
-
- if (
- children.length === 1 && children[0].kind === "text" &&
- attributes.size === 0
- ) {
- const content = children[0].content;
- return {
- kind: "text",
- content: `\u2018${content}\u2019`,
- raw: false,
- };
- } else {
- return {
- kind: "element",
- name: "span",
- attributes: convertAttributes(node.attributes),
- children,
- };
- }
-}
-
-function processSmartPunctuation(node: DjotSmartPunctuation): Node {
- // Map smart punctuation types to Unicode characters
- const punctuationMap: Record<string, string> = {
- "left_single_quote": "\u2018", // '
- "right_single_quote": "\u2019", // '
- "left_double_quote": "\u201C", // "
- "right_double_quote": "\u201D", // "
- "ellipses": "\u2026", // …
- "em_dash": "\u2014", // —
- "en_dash": "\u2013", // –
- };
-
- return {
- kind: "text",
- content: punctuationMap[node.type] || node.text,
- raw: false,
- };
-}
-
-function processDiv(node: DjotDiv): Element {
- if (node.attributes?.class === "note") {
- delete node.attributes.class;
- return {
- kind: "element",
- name: "note",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processBlock),
- };
- }
-
- if (node.attributes?.class === "edit") {
- delete node.attributes.class;
- return {
- kind: "element",
- name: "note",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processBlock),
- };
- }
-
- return {
- kind: "element",
- name: "div",
- attributes: convertAttributes(node.attributes),
- children: node.children.map(processBlock),
- };
-}
-
-function processRawBlock(node: DjotRawBlock): Element {
- // If the format is HTML, wrap the HTML content in a div
- if (node.format === "html" || node.format === "HTML") {
- return {
- kind: "element",
- name: "div",
- attributes: new Map([["class", "raw-html"]]),
- children: [
- {
- kind: "text",
- content: node.text,
- raw: true,
- },
- ],
- };
- }
-
- // For other formats, wrap in a pre tag
- return {
- kind: "element",
- name: "pre",
- attributes: new Map([["data-format", node.format]]),
- children: [
- {
- kind: "text",
- content: node.text,
- raw: false,
- },
- ],
- };
-}
-
-// Helper function to convert Djot attributes to Nuldoc attributes
-function convertAttributes(
- attrs?: Record<string, string>,
-): Map<string, string> {
- const result = new Map<string, string>();
- if (attrs) {
- for (const [key, value] of Object.entries(attrs)) {
- result.set(key, value);
- }
- }
- return result;
-}
-
-export function djot2ndoc(doc: DjotDoc): Element {
- const children: Node[] = [];
- for (const child of doc.children) {
- children.push(processBlock(child));
- }
-
- // Process footnotes if any exist
- if (doc.footnotes && Object.keys(doc.footnotes).length > 0) {
- const footnoteSection: Element = {
- kind: "element",
- name: "section",
- attributes: new Map([["class", "footnotes"]]),
- children: [],
- };
-
- for (const [id, footnote] of Object.entries(doc.footnotes)) {
- const footnoteElement: Element = {
- kind: "element",
- name: "footnote",
- attributes: new Map([["id", id]]),
- children: footnote.children.map(processBlock),
- };
- footnoteSection.children.push(footnoteElement);
- }
-
- children.push(footnoteSection);
- }
-
- return {
- kind: "element",
- name: "__root__",
- attributes: new Map(),
- children: [{
- kind: "element",
- name: "article",
- attributes: new Map(),
- children,
- }],
- };
-}
diff --git a/vhosts/blog/nuldoc-src/djot/document.ts b/vhosts/blog/nuldoc-src/djot/document.ts
deleted file mode 100644
index be9c08d5..00000000
--- a/vhosts/blog/nuldoc-src/djot/document.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Doc as DjotDoc } from "@djot/djot";
-import { join } from "@std/path";
-import { z } from "zod/mod.ts";
-import { Config } from "../config.ts";
-import { Element } from "../dom.ts";
-import { Revision, stringToDate } from "../revision.ts";
-import { djot2ndoc } from "./djot2ndoc.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 createNewDocumentFromDjotDocument(
- root: DjotDoc,
- meta: PostMetadata,
- sourceFilePath: string,
- config: Config,
-): Document {
- const cwd = Deno.cwd();
- const contentDir = join(cwd, config.locations.contentDir);
- const link = sourceFilePath.replace(contentDir, "").replace(".xml", "/");
- return {
- root: djot2ndoc(root),
- 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/djot/parse.ts b/vhosts/blog/nuldoc-src/djot/parse.ts
deleted file mode 100644
index c79a6708..00000000
--- a/vhosts/blog/nuldoc-src/djot/parse.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { parse as parseDjot } from "@djot/djot";
-import { parse as parseToml } from "@std/toml";
-import { Config } from "../config.ts";
-import {
- createNewDocumentFromDjotDocument,
- Document,
- PostMetadata,
- PostMetadataSchema,
-} from "./document.ts";
-import toHtml from "./to_html.ts";
-
-export async function parseDjotFile(
- filePath: string,
- config: Config,
-): Promise<Document> {
- try {
- const fileContent = await Deno.readTextFile(filePath);
- const [, frontmatter, ...rest] = fileContent.split(/^---$/m);
- const meta = parseMetadata(frontmatter);
- const root = parseDjot(rest.join("\n"));
- const doc = createNewDocumentFromDjotDocument(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/djot/to_html.ts b/vhosts/blog/nuldoc-src/djot/to_html.ts
deleted file mode 100644
index 5ea9b57d..00000000
--- a/vhosts/blog/nuldoc-src/djot/to_html.ts
+++ /dev/null
@@ -1,449 +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> {
- mergeConsecutiveTextNodes(doc);
- removeUnnecessaryTextNode(doc);
- transformLinkLikeToAnchorElement(doc);
- transformSectionIdAttribute(doc);
- setSectionTitleAnchor(doc);
- transformSectionTitleElement(doc);
- transformNoteElement(doc);
- addAttributesToExternalLinkElement(doc);
- traverseFootnotes(doc);
- removeUnnecessaryParagraphNode(doc);
- await transformAndHighlightCodeBlockElement(doc);
- mergeConsecutiveTextNodes(doc);
- return doc;
-}
-
-function mergeConsecutiveTextNodes(doc: Document) {
- forEachChildRecursively(doc.root, (n) => {
- if (n.kind !== "element") {
- return;
- }
-
- const newChildren: Node[] = [];
- let currentTextContent = "";
-
- for (const child of n.children) {
- if (child.kind === "text" && !child.raw) {
- currentTextContent += child.content;
- } else {
- if (currentTextContent !== "") {
- newChildren.push({
- kind: "text",
- content: currentTextContent,
- raw: false,
- });
- currentTextContent = "";
- }
- newChildren.push(child);
- }
- }
-
- if (currentTextContent !== "") {
- newChildren.push({
- kind: "text",
- content: currentTextContent,
- raw: false,
- });
- }
-
- n.children = newChildren;
- });
-}
-
-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 editatAttr = n.attributes?.get("editat");
- const operationAttr = n.attributes?.get("operation");
- const isEditBlock = editatAttr && operationAttr;
-
- const labelElement: Element = {
- kind: "element",
- name: "div",
- attributes: new Map([["class", "admonition-label"]]),
- children: [{
- kind: "text",
- content: isEditBlock ? `${editatAttr} ${operationAttr}` : "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 traverseFootnotes(doc: Document) {
- let footnoteCounter = 0;
- const footnoteMap = new Map<string, number>();
-
- forEachChildRecursively(doc.root, (n) => {
- if (n.kind !== "element" || n.name !== "footnoteref") {
- return;
- }
-
- const reference = n.attributes.get("reference");
- if (!reference) {
- return;
- }
-
- let footnoteNumber: number;
- if (footnoteMap.has(reference)) {
- footnoteNumber = footnoteMap.get(reference)!;
- } else {
- footnoteNumber = ++footnoteCounter;
- footnoteMap.set(reference, footnoteNumber);
- }
-
- n.name = "sup";
- n.attributes.delete("reference");
- n.attributes.set("class", "footnote");
- n.children = [
- {
- kind: "element",
- name: "a",
- attributes: new Map([
- ["id", `footnoteref--${reference}`],
- ["class", "footnote"],
- ["href", `#footnote--${reference}`],
- ]),
- children: [
- {
- kind: "text",
- content: `[${footnoteNumber}]`,
- raw: false,
- },
- ],
- },
- ];
- });
-
- forEachChildRecursively(doc.root, (n) => {
- if (n.kind !== "element" || n.name !== "footnote") {
- return;
- }
-
- const id = n.attributes.get("id");
- if (!id || !footnoteMap.has(id)) {
- n.name = "span";
- n.children = [];
- return;
- }
-
- const footnoteNumber = footnoteMap.get(id)!;
-
- n.name = "div";
- n.attributes.delete("id");
- n.attributes.set("class", "footnote");
- n.attributes.set("id", `footnote--${id}`);
-
- n.children = [
- {
- kind: "element",
- name: "a",
- attributes: new Map([["href", `#footnoteref--${id}`]]),
- children: [
- {
- kind: "text",
- content: `${footnoteNumber}. `,
- raw: false,
- },
- ],
- },
- ...n.children,
- ];
- });
-}
-
-function removeUnnecessaryParagraphNode(doc: Document) {
- forEachChildRecursively(doc.root, (n) => {
- if (n.kind !== "element" || (n.name !== "ul" && n.name !== "ol")) {
- return;
- }
-
- const isTight = n.attributes.get("--tight") === "true";
- if (!isTight) {
- return;
- }
-
- for (const child of n.children) {
- if (child.kind !== "element" || child.name !== "li") {
- continue;
- }
- const newGrandChildren: Node[] = [];
- for (const grandChild of child.children) {
- if (grandChild.kind === "element" && grandChild.name === "p") {
- newGrandChildren.push(...grandChild.children);
- } else {
- newGrandChildren.push(grandChild);
- }
- }
- child.children = newGrandChildren;
- }
- });
-}
-
-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 filename = n.attributes.get("filename");
- const numbered = n.attributes.get("numbered");
- const sourceCodeNode = n.children[0] as Text | RawHTML;
- const sourceCode = sourceCodeNode.content.trimEnd();
-
- const highlighted = await codeToHtml(sourceCode, {
- lang: language in bundledLanguages ? language as BundledLanguage : "text",
- theme: "github-light",
- colorReplacements: {
- "#fff": "#f5f5f5",
- },
- });
-
- n.name = "div";
- n.attributes.set("class", "codeblock");
- n.attributes.delete("language");
-
- if (numbered === "true") {
- n.attributes.delete("numbered");
- addClass(n, "numbered");
- }
- if (filename) {
- n.attributes.delete("filename");
-
- n.children = [
- {
- kind: "element",
- name: "div",
- attributes: new Map([["class", "filename"]]),
- children: [{
- kind: "text",
- content: filename,
- raw: false,
- }],
- },
- {
- kind: "text",
- content: highlighted,
- raw: true,
- },
- ];
- } else {
- sourceCodeNode.content = highlighted;
- sourceCodeNode.raw = true;
- }
- });
-}
diff --git a/vhosts/blog/nuldoc-src/dom.ts b/vhosts/blog/nuldoc-src/dom.ts
deleted file mode 100644
index ed7ffd31..00000000
--- a/vhosts/blog/nuldoc-src/dom.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-export type Text = {
- kind: "text";
- content: string;
- raw: false;
-};
-
-export type RawHTML = {
- kind: "text";
- content: string;
- raw: true;
-};
-
-export type Element = {
- kind: "element";
- name: string;
- attributes: Map<string, string>;
- children: Node[];
-};
-
-export type Node = Element | Text | RawHTML;
-
-export function addClass(e: Element, klass: string) {
- const classes = e.attributes.get("class");
- if (classes === undefined) {
- e.attributes.set("class", klass);
- } else {
- const classList = classes.split(" ");
- classList.push(klass);
- classList.sort();
- e.attributes.set("class", classList.join(" "));
- }
-}
-
-export function findFirstChildElement(
- e: Element,
- name: string,
-): Element | null {
- for (const c of e.children) {
- if (c.kind === "element" && c.name === name) {
- return c;
- }
- }
- return null;
-}
-
-export function findChildElements(e: Element, name: string): Element[] {
- const cs = [];
- for (const c of e.children) {
- if (c.kind === "element" && c.name === name) {
- cs.push(c);
- }
- }
- return cs;
-}
-
-export function innerText(e: Element): string {
- let t = "";
- forEachChild(e, (c) => {
- if (c.kind === "text") {
- t += c.content;
- }
- });
- return t;
-}
-
-export function forEachChild(e: Element, f: (n: Node) => void) {
- for (const c of e.children) {
- f(c);
- }
-}
-
-export async function forEachChildAsync(
- e: Element,
- f: (n: Node) => Promise<void>,
-): Promise<void> {
- for (const c of e.children) {
- await f(c);
- }
-}
-
-export function forEachChildRecursively(e: Element, f: (n: Node) => void) {
- const g = (c: Node) => {
- f(c);
- if (c.kind === "element") {
- forEachChild(c, g);
- }
- };
- forEachChild(e, g);
-}
-
-export async function forEachChildRecursivelyAsync(
- e: Element,
- f: (n: Node) => Promise<void>,
-): Promise<void> {
- const g = async (c: Node) => {
- await f(c);
- if (c.kind === "element") {
- await forEachChildAsync(c, g);
- }
- };
- await forEachChildAsync(e, g);
-}
diff --git a/vhosts/blog/nuldoc-src/errors.ts b/vhosts/blog/nuldoc-src/errors.ts
deleted file mode 100644
index 1692a4c8..00000000
--- a/vhosts/blog/nuldoc-src/errors.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export class NuldocError extends Error {
- static {
- this.prototype.name = "NuldocError";
- }
-}
-
-export class SlideError extends Error {
- static {
- this.prototype.name = "SlideError";
- }
-}
-
-export class XmlParseError extends Error {
- static {
- this.prototype.name = "XmlParseError";
- }
-}
diff --git a/vhosts/blog/nuldoc-src/generators/about.ts b/vhosts/blog/nuldoc-src/generators/about.ts
deleted file mode 100644
index 6663a190..00000000
--- a/vhosts/blog/nuldoc-src/generators/about.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import AboutPage from "../pages/AboutPage.tsx";
-import { Config } from "../config.ts";
-import { renderToDOM } from "../jsx/render.ts";
-import { Page } from "../page.ts";
-import { SlidePage } from "./slide.ts";
-
-export type AboutPage = Page;
-
-export async function generateAboutPage(
- slides: SlidePage[],
- config: Config,
-): Promise<AboutPage> {
- const html = await renderToDOM(
- AboutPage(slides, config),
- );
-
- return {
- root: html,
- renderer: "html",
- destFilePath: "/about/index.html",
- href: "/about/",
- };
-}
diff --git a/vhosts/blog/nuldoc-src/generators/atom.ts b/vhosts/blog/nuldoc-src/generators/atom.ts
deleted file mode 100644
index 6ad07b46..00000000
--- a/vhosts/blog/nuldoc-src/generators/atom.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { Config } from "../config.ts";
-import { Page } from "../page.ts";
-import { PostPage } from "../generators/post.ts";
-import { SlidePage } from "../generators/slide.ts";
-import { dateToRfc3339String } from "../revision.ts";
-import AtomPage from "../pages/AtomPage.tsx";
-import { renderToDOM } from "../jsx/render.ts";
-
-export type Feed = {
- author: string;
- icon: string;
- id: string;
- linkToSelf: string;
- linkToAlternate: string;
- title: string;
- updated: string;
- entries: Entry[];
-};
-
-export type Entry = {
- id: string;
- linkToAlternate: string;
- published: string;
- summary: string;
- title: string;
- updated: string;
-};
-
-const BASE_NAME = "atom.xml";
-
-export async function generateFeedPageFromEntries(
- alternateLink: string,
- feedSlug: string,
- feedTitle: string,
- entries: Array<PostPage | SlidePage>,
- config: Config,
-): Promise<Page> {
- const entries_: Entry[] = [];
- for (const entry of entries) {
- entries_.push({
- id: `urn:uuid:${entry.uuid}`,
- linkToAlternate: `https://${config.blog.fqdn}${entry.href}`,
- title: entry.title,
- summary: entry.description,
- published: dateToRfc3339String(entry.published),
- updated: dateToRfc3339String(entry.updated),
- });
- }
- // Sort by published date in ascending order.
- entries_.sort((a, b) => {
- if (a.published < b.published) {
- return 1;
- } else if (a.published > b.published) {
- return -1;
- }
- return 0;
- });
- const feedPath = `${alternateLink}${BASE_NAME}`;
- const feed: Feed = {
- author: config.blog.author,
- icon: `https://${config.blog.fqdn}/favicon.svg`,
- id: `tag:${config.blog.fqdn},${config.blog.siteCopyrightYear}:${feedSlug}`,
- linkToSelf: `https://${config.blog.fqdn}${feedPath}`,
- linkToAlternate: `https://${config.blog.fqdn}${alternateLink}`,
- title: feedTitle,
- updated: entries_.reduce(
- (latest, entry) => entry.updated > latest ? entry.updated : latest,
- entries_[0].updated,
- ),
- entries: entries_,
- };
-
- return {
- root: await renderToDOM(AtomPage({ feed: feed })),
- renderer: "xml",
- destFilePath: feedPath,
- href: feedPath,
- };
-}
diff --git a/vhosts/blog/nuldoc-src/generators/home.ts b/vhosts/blog/nuldoc-src/generators/home.ts
deleted file mode 100644
index 679dd39a..00000000
--- a/vhosts/blog/nuldoc-src/generators/home.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import HomePage from "../pages/HomePage.tsx";
-import { renderToDOM } from "../jsx/render.ts";
-import { Config } from "../config.ts";
-import { Page } from "../page.ts";
-
-export type HomePage = Page;
-
-export async function generateHomePage(config: Config): Promise<HomePage> {
- const html = await renderToDOM(
- HomePage(config),
- );
-
- return {
- root: html,
- renderer: "html",
- destFilePath: "/index.html",
- href: "/",
- };
-}
diff --git a/vhosts/blog/nuldoc-src/generators/not_found.ts b/vhosts/blog/nuldoc-src/generators/not_found.ts
deleted file mode 100644
index f5a81c86..00000000
--- a/vhosts/blog/nuldoc-src/generators/not_found.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import NotFoundPage from "../pages/NotFoundPage.tsx";
-import { renderToDOM } from "../jsx/render.ts";
-import { Config } from "../config.ts";
-import { Page } from "../page.ts";
-
-export type NotFoundPage = Page;
-
-export async function generateNotFoundPage(
- config: Config,
-): Promise<NotFoundPage> {
- const html = await renderToDOM(
- NotFoundPage(config),
- );
-
- return {
- root: html,
- renderer: "html",
- destFilePath: "/404.html",
- href: "/404.html",
- };
-}
diff --git a/vhosts/blog/nuldoc-src/generators/post.ts b/vhosts/blog/nuldoc-src/generators/post.ts
deleted file mode 100644
index 0e2a9553..00000000
--- a/vhosts/blog/nuldoc-src/generators/post.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { join } from "@std/path";
-import { renderToDOM } from "../jsx/render.ts";
-import PostPage from "../pages/PostPage.tsx";
-import { Config } from "../config.ts";
-import { Document } from "../djot/document.ts";
-import { Page } from "../page.ts";
-import { Date, Revision } from "../revision.ts";
-
-export interface PostPage extends Page {
- title: string;
- description: string;
- tags: string[];
- revisions: Revision[];
- published: Date;
- updated: Date;
- uuid: string;
-}
-
-export function getPostPublishedDate(page: { revisions: Revision[] }): Date {
- for (const rev of page.revisions) {
- if (!rev.isInternal) {
- return rev.date;
- }
- }
- return page.revisions[0].date;
-}
-
-export function getPostUpdatedDate(page: { revisions: Revision[] }): Date {
- return page.revisions[page.revisions.length - 1].date;
-}
-
-export function postHasAnyUpdates(page: { revisions: Revision[] }): boolean {
- return 2 <= page.revisions.filter((rev) => !rev.isInternal).length;
-}
-
-export async function generatePostPage(
- doc: Document,
- config: Config,
-): Promise<PostPage> {
- const html = await renderToDOM(
- PostPage(doc, config),
- );
-
- const cwd = Deno.cwd();
- const contentDir = join(cwd, config.locations.contentDir);
- const destFilePath = join(
- doc.sourceFilePath.replace(contentDir, "").replace(".dj", ""),
- "index.html",
- );
- return {
- root: html,
- renderer: "html",
- destFilePath: destFilePath,
- href: destFilePath.replace("index.html", ""),
- title: doc.title,
- description: doc.description,
- tags: doc.tags,
- revisions: doc.revisions,
- published: getPostPublishedDate(doc),
- updated: getPostUpdatedDate(doc),
- uuid: doc.uuid,
- };
-}
diff --git a/vhosts/blog/nuldoc-src/generators/post_list.ts b/vhosts/blog/nuldoc-src/generators/post_list.ts
deleted file mode 100644
index b05f7ee6..00000000
--- a/vhosts/blog/nuldoc-src/generators/post_list.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { renderToDOM } from "../jsx/render.ts";
-import PostListPage from "../pages/PostListPage.tsx";
-import { Config } from "../config.ts";
-import { Page } from "../page.ts";
-import { PostPage } from "./post.ts";
-
-export type PostListPage = Page;
-
-export async function generatePostListPages(
- posts: PostPage[],
- config: Config,
-): Promise<PostListPage[]> {
- const postsPerPage = config.blog.postsPerPage;
- const totalPages = Math.ceil(posts.length / postsPerPage);
- const pages: PostListPage[] = [];
-
- for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
- const pagePosts = posts.slice(
- pageIndex * postsPerPage,
- (pageIndex + 1) * postsPerPage,
- );
-
- const page = await generatePostListPage(
- pagePosts,
- config,
- pageIndex + 1,
- totalPages,
- );
-
- pages.push(page);
- }
-
- return pages;
-}
-
-async function generatePostListPage(
- posts: PostPage[],
- config: Config,
- currentPage: number,
- totalPages: number,
-): Promise<PostListPage> {
- const html = await renderToDOM(
- PostListPage(
- posts,
- config,
- currentPage,
- totalPages,
- ),
- );
-
- const destFilePath = currentPage === 1
- ? "/posts/index.html"
- : `/posts/${currentPage}/index.html`;
-
- const href = currentPage === 1 ? "/posts/" : `/posts/${currentPage}/`;
-
- return {
- root: html,
- renderer: "html",
- destFilePath,
- href,
- };
-}
diff --git a/vhosts/blog/nuldoc-src/generators/slide.ts b/vhosts/blog/nuldoc-src/generators/slide.ts
deleted file mode 100644
index cd28879f..00000000
--- a/vhosts/blog/nuldoc-src/generators/slide.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { join } from "@std/path";
-import { renderToDOM } from "../jsx/render.ts";
-import SlidePage from "../pages/SlidePage.tsx";
-import { Config } from "../config.ts";
-import { Page } from "../page.ts";
-import { Date, Revision } from "../revision.ts";
-import { Slide } from "../slide/slide.ts";
-import { getPostPublishedDate, getPostUpdatedDate } from "./post.ts";
-
-export interface SlidePage extends Page {
- title: string;
- description: string;
- event: string;
- talkType: string;
- slideLink: string;
- tags: string[];
- revisions: Revision[];
- published: Date;
- updated: Date;
- uuid: string;
-}
-
-export async function generateSlidePage(
- slide: Slide,
- config: Config,
-): Promise<SlidePage> {
- const html = await renderToDOM(
- SlidePage(slide, config),
- );
-
- const cwd = Deno.cwd();
- const contentDir = join(cwd, config.locations.contentDir);
- const destFilePath = join(
- slide.sourceFilePath.replace(contentDir, "").replace(".toml", ""),
- "index.html",
- );
- return {
- root: html,
- renderer: "html",
- destFilePath: destFilePath,
- href: destFilePath.replace("index.html", ""),
- title: slide.title,
- description: `登壇: ${slide.event} (${slide.talkType})`,
- event: slide.event,
- talkType: slide.talkType,
- slideLink: slide.slideLink,
- tags: slide.tags,
- revisions: slide.revisions,
- published: getPostPublishedDate(slide),
- updated: getPostUpdatedDate(slide),
- uuid: slide.uuid,
- };
-}
diff --git a/vhosts/blog/nuldoc-src/generators/slide_list.ts b/vhosts/blog/nuldoc-src/generators/slide_list.ts
deleted file mode 100644
index abebe109..00000000
--- a/vhosts/blog/nuldoc-src/generators/slide_list.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { renderToDOM } from "../jsx/render.ts";
-import SlideListPage from "../pages/SlideListPage.tsx";
-import { Config } from "../config.ts";
-import { Page } from "../page.ts";
-import { SlidePage } from "./slide.ts";
-
-export type SlideListPage = Page;
-
-export async function generateSlideListPage(
- slides: SlidePage[],
- config: Config,
-): Promise<SlideListPage> {
- const html = await renderToDOM(
- SlideListPage(slides, config),
- );
-
- return {
- root: html,
- renderer: "html",
- destFilePath: "/slides/index.html",
- href: "/slides/",
- };
-}
diff --git a/vhosts/blog/nuldoc-src/generators/tag.ts b/vhosts/blog/nuldoc-src/generators/tag.ts
deleted file mode 100644
index dbd8ef93..00000000
--- a/vhosts/blog/nuldoc-src/generators/tag.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { renderToDOM } from "../jsx/render.ts";
-import TagPage from "../pages/TagPage.tsx";
-import { Config, getTagLabel } from "../config.ts";
-import { Page } from "../page.ts";
-import { TaggedPage } from "./tagged_page.ts";
-
-export interface TagPage extends Page {
- tagSlug: string;
- tagLabel: string;
- numOfPosts: number;
- numOfSlides: number;
-}
-
-export async function generateTagPage(
- tagSlug: string,
- pages: TaggedPage[],
- config: Config,
-): Promise<TagPage> {
- const html = await renderToDOM(
- TagPage(tagSlug, pages, config),
- );
-
- return {
- root: html,
- renderer: "html",
- destFilePath: `/tags/${tagSlug}/index.html`,
- href: `/tags/${tagSlug}/`,
- tagSlug: tagSlug,
- tagLabel: getTagLabel(config, tagSlug),
- numOfPosts: pages.filter((p) => !("event" in p)).length,
- numOfSlides: pages.filter((p) => "event" in p).length,
- };
-}
diff --git a/vhosts/blog/nuldoc-src/generators/tag_list.ts b/vhosts/blog/nuldoc-src/generators/tag_list.ts
deleted file mode 100644
index 7baad8cf..00000000
--- a/vhosts/blog/nuldoc-src/generators/tag_list.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { renderToDOM } from "../jsx/render.ts";
-import TagListPage from "../pages/TagListPage.tsx";
-import { Config } from "../config.ts";
-import { Page } from "../page.ts";
-import { TagPage } from "./tag.ts";
-
-export type TagListPage = Page;
-
-export async function generateTagListPage(
- tags: TagPage[],
- config: Config,
-): Promise<TagListPage> {
- const html = await renderToDOM(
- TagListPage(tags, config),
- );
-
- return {
- root: html,
- renderer: "html",
- destFilePath: "/tags/index.html",
- href: "/tags/",
- };
-}
diff --git a/vhosts/blog/nuldoc-src/generators/tagged_page.ts b/vhosts/blog/nuldoc-src/generators/tagged_page.ts
deleted file mode 100644
index 23de8cb4..00000000
--- a/vhosts/blog/nuldoc-src/generators/tagged_page.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { PostPage } from "./post.ts";
-import { SlidePage } from "./slide.ts";
-
-export type TaggedPage = PostPage | SlidePage;
diff --git a/vhosts/blog/nuldoc-src/jsx/jsx-runtime.ts b/vhosts/blog/nuldoc-src/jsx/jsx-runtime.ts
deleted file mode 100644
index 9571e87d..00000000
--- a/vhosts/blog/nuldoc-src/jsx/jsx-runtime.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { Node } from "../dom.ts";
-
-export type JSXElement = {
- tag: string | FunctionComponent;
- props: Props;
-};
-
-export type JSXNullNode = false | null | undefined;
-export type JSXSimpleNode = JSXElement | Node | string;
-export type JSXNullableSimpleNode = JSXSimpleNode | JSXNullNode;
-export type JSXNode = JSXNullableSimpleNode | JSXNode[];
-export type RenderableJSXNode = JSXElement;
-
-type Props = { children?: JSXNode } & Record<string, unknown>;
-export type FunctionComponentResult = JSXElement | Promise<JSXElement>;
-type FunctionComponent = (props: Props) => FunctionComponentResult;
-
-export function jsx(
- tag: string | FunctionComponent,
- props: Props,
-): JSXElement {
- return { tag, props };
-}
-
-export { jsx as jsxs };
-
-// TODO: support Fragment
diff --git a/vhosts/blog/nuldoc-src/jsx/render.ts b/vhosts/blog/nuldoc-src/jsx/render.ts
deleted file mode 100644
index 8603f6c3..00000000
--- a/vhosts/blog/nuldoc-src/jsx/render.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import type { Element, Node } from "../dom.ts";
-import type {
- JSXNode,
- JSXNullableSimpleNode,
- JSXSimpleNode,
- RenderableJSXNode,
-} from "myjsx/jsx-runtime";
-
-function transformNode(node: JSXNode): Promise<Node[]> {
- const flattenNodes: JSXNullableSimpleNode[] = Array.isArray(node)
- // @ts-ignore prevents infinite recursion
- ? (node.flat(Infinity) as JSXNullableSimpleNode[])
- : [node];
- return Promise.all(
- flattenNodes
- .filter((c): c is JSXSimpleNode => c != null && c !== false)
- .map((c) => {
- if (typeof c === "string") {
- return { kind: "text", content: c, raw: false };
- } else if ("kind" in c) {
- return c;
- } else {
- return renderToDOM(c);
- }
- }),
- );
-}
-
-export async function renderToDOM(
- element: RenderableJSXNode,
-): Promise<Element> {
- const { tag, props } = element;
- if (typeof tag === "string") {
- const { children, ...attrs } = props;
- const attrsMap = new Map(Object.entries(attrs)) as Map<string, string>;
- return {
- kind: "element",
- name: tag,
- attributes: attrsMap,
- children: await transformNode(children),
- };
- } else {
- return renderToDOM(await tag(props));
- }
-}
diff --git a/vhosts/blog/nuldoc-src/jsx/types.d.ts b/vhosts/blog/nuldoc-src/jsx/types.d.ts
deleted file mode 100644
index 0e5b223f..00000000
--- a/vhosts/blog/nuldoc-src/jsx/types.d.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import type {
- FunctionComponentResult,
- JSXElement,
- JSXNode,
-} from "myjsx/jsx-runtime";
-
-export { JSXNode };
-
-interface IntrinsicElementType {
- children?: JSXNode;
- className?: string;
- id?: string;
- // My JSX runtime does not use key. It is only for linter that complains about missing key.
- key?: string;
-}
-
-declare global {
- namespace JSX {
- type Element = JSXElement;
- type ElementType =
- | string
- // deno-lint-ignore no-explicit-any
- | ((props: any) => FunctionComponentResult);
-
- // TODO: HTML 用の element と XML 用の element を分ける
- interface IntrinsicElements {
- // XML (Atom)
- author: IntrinsicElementType;
- entry: IntrinsicElementType;
- feed: IntrinsicElementType & { xmlns: string };
- id: IntrinsicElementType;
- name: IntrinsicElementType;
- published: IntrinsicElementType;
- summary: IntrinsicElementType;
- updated: IntrinsicElementType;
- // HTML
- a: IntrinsicElementType & {
- href?: string;
- rel?: "noreferrer";
- target?: "_blank";
- };
- article: IntrinsicElementType;
- body: IntrinsicElementType;
- button: IntrinsicElementType & { type: string };
- canvas: { id?: string; "data-slide-link"?: string };
- div: IntrinsicElementType;
- footer: IntrinsicElementType;
- h1: IntrinsicElementType;
- h2: IntrinsicElementType;
- head: unknown;
- header: IntrinsicElementType;
- html: IntrinsicElementType & { lang?: string };
- img: { src: string };
- li: IntrinsicElementType;
- link: { rel: string; href: string; type?: string };
- main: IntrinsicElementType;
- meta: {
- charset?: string;
- name?: string;
- content?: string;
- property?: string;
- };
- nav: IntrinsicElementType;
- noscript: IntrinsicElementType;
- ol: IntrinsicElementType;
- p: IntrinsicElementType;
- script: { src: string; type?: string; defer?: "true" };
- section: IntrinsicElementType;
- time: IntrinsicElementType & { datetime?: string };
- title: IntrinsicElementType;
- ul: IntrinsicElementType;
- }
-
- interface ElementChildrenAttribute {
- children: unknown;
- }
-
- type LibraryManagedAttributes<_F, P> = P & {
- // My JSX runtime does not use key. It is only for linter that complains about missing key.
- key?: string;
- };
- }
-}
diff --git a/vhosts/blog/nuldoc-src/main.ts b/vhosts/blog/nuldoc-src/main.ts
deleted file mode 100644
index af6acc2e..00000000
--- a/vhosts/blog/nuldoc-src/main.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { runBuildCommand } from "./commands/build.ts";
-import { runNewCommand } from "./commands/new.ts";
-import { runServeCommand } from "./commands/serve.ts";
-import { getDefaultConfigPath, loadConfig } from "./config.ts";
-
-const config = await loadConfig(getDefaultConfigPath());
-
-if (import.meta.main) {
- const command = Deno.args[0] ?? "build";
- if (command === "build") {
- await runBuildCommand(config);
- } else if (command === "new") {
- runNewCommand(config);
- } else if (command === "serve") {
- runServeCommand(config);
- } else {
- console.error(`Unknown command: ${command}`);
- }
-}
diff --git a/vhosts/blog/nuldoc-src/page.ts b/vhosts/blog/nuldoc-src/page.ts
deleted file mode 100644
index f4a6166b..00000000
--- a/vhosts/blog/nuldoc-src/page.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Element } from "./dom.ts";
-import { RendererType } from "./render.ts";
-
-export interface Page {
- root: Element;
- renderer: RendererType;
- destFilePath: string;
- href: string;
-}
diff --git a/vhosts/blog/nuldoc-src/pages/AboutPage.tsx b/vhosts/blog/nuldoc-src/pages/AboutPage.tsx
deleted file mode 100644
index 3d6583a4..00000000
--- a/vhosts/blog/nuldoc-src/pages/AboutPage.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import GlobalFooter from "../components/GlobalFooter.tsx";
-import GlobalHeader from "../components/GlobalHeader.tsx";
-import PageLayout from "../components/PageLayout.tsx";
-import StaticScript from "../components/StaticScript.tsx";
-import { Config } from "../config.ts";
-import { dateToString } from "../revision.ts";
-import { getPostPublishedDate } from "../generators/post.ts";
-import { SlidePage } from "../generators/slide.ts";
-
-export default function AboutPage(
- slides: SlidePage[],
- config: Config,
-) {
- return (
- <PageLayout
- metaCopyrightYear={config.blog.siteCopyrightYear}
- metaDescription="このサイトの著者について"
- metaTitle={`About|${config.blog.siteName}`}
- config={config}
- >
- <body className="single">
- <GlobalHeader config={config} />
- <main className="main">
- <article className="post-single">
- <header className="post-header">
- <h1 className="post-title">nsfisis</h1>
- <div className="my-icon">
- <div id="myIcon">
- <img src="/favicon.svg" />
- </div>
- <StaticScript
- fileName="/my-icon.js"
- defer="true"
- config={config}
- />
- </div>
- </header>
- <div className="post-content">
- <section>
- <h2>読み方</h2>
- <p>
- 読み方は決めていません。音にする必要があるときは本名である「いまむら」をお使いください。
- </p>
- </section>
- <section>
- <h2>アカウント</h2>
- <ul>
- <li>
- <a
- href="https://twitter.com/nsfisis"
- target="_blank"
- rel="noreferrer"
- >
- Twitter (現 𝕏): @nsfisis
- </a>
- </li>
- <li>
- <a
- href="https://github.com/nsfisis"
- target="_blank"
- rel="noreferrer"
- >
- GitHub: @nsfisis
- </a>
- </li>
- </ul>
- </section>
- <section>
- <h2>仕事</h2>
- <ul>
- <li>
- {"2021-01~現在: "}
- <a
- href="https://www.dgcircus.com/"
- target="_blank"
- rel="noreferrer"
- >
- デジタルサーカス株式会社
- </a>
- </li>
- </ul>
- </section>
- <section>
- <h2>登壇</h2>
- <ul>
- {Array.from(slides).sort((a, b) => {
- const ta = dateToString(getPostPublishedDate(a));
- const tb = dateToString(getPostPublishedDate(b));
- if (ta > tb) return -1;
- if (ta < tb) return 1;
- return 0;
- }).map((slide) => (
- <li>
- <a href={slide.href}>
- {`${
- dateToString(getPostPublishedDate(slide))
- }: ${slide.event} (${slide.talkType})`}
- </a>
- </li>
- ))}
- </ul>
- </section>
- </div>
- </article>
- </main>
- <GlobalFooter config={config} />
- </body>
- </PageLayout>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/pages/AtomPage.tsx b/vhosts/blog/nuldoc-src/pages/AtomPage.tsx
deleted file mode 100644
index 21c3bfaf..00000000
--- a/vhosts/blog/nuldoc-src/pages/AtomPage.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Feed } from "../generators/atom.ts";
-
-export default function AtomPage({ feed }: { feed: Feed }) {
- return (
- <feed xmlns="http://www.w3.org/2005/Atom">
- <id>{feed.id}</id>
- <title>{feed.title}</title>
- <link rel="alternate" href={feed.linkToAlternate} />
- <link rel="self" href={feed.linkToSelf} />
- <author>
- <name>{feed.author}</name>
- </author>
- <updated>{feed.updated}</updated>
- {feed.entries.map((entry) => (
- <entry>
- <id>{entry.id}</id>
- <link rel="alternate" href={entry.linkToAlternate} />
- <title>{entry.title}</title>
- <summary>{entry.summary}</summary>
- <published>{entry.published}</published>
- <updated>{entry.updated}</updated>
- </entry>
- ))}
- </feed>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/pages/HomePage.tsx b/vhosts/blog/nuldoc-src/pages/HomePage.tsx
deleted file mode 100644
index 8850d039..00000000
--- a/vhosts/blog/nuldoc-src/pages/HomePage.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import GlobalFooter from "../components/GlobalFooter.tsx";
-import GlobalHeader from "../components/GlobalHeader.tsx";
-import PageLayout from "../components/PageLayout.tsx";
-import { Config } from "../config.ts";
-
-export default function HomePage(config: Config) {
- return (
- <PageLayout
- metaCopyrightYear={config.blog.siteCopyrightYear}
- metaDescription="nsfisis のブログサイト"
- metaTitle={config.blog.siteName}
- metaAtomFeedHref={`https://${config.blog.fqdn}/atom.xml`}
- config={config}
- >
- <body className="single">
- <GlobalHeader config={config} />
- <main className="main">
- <article className="post-single">
- <article className="post-entry">
- <a href="/about/">
- <header className="entry-header">
- <h2>About</h2>
- </header>
- </a>
- </article>
- <article className="post-entry">
- <a href="/posts/">
- <header className="entry-header">
- <h2>Posts</h2>
- </header>
- </a>
- </article>
- <article className="post-entry">
- <a href="/slides/">
- <header className="entry-header">
- <h2>Slides</h2>
- </header>
- </a>
- </article>
- <article className="post-entry">
- <a href="/tags/">
- <header className="entry-header">
- <h2>Tags</h2>
- </header>
- </a>
- </article>
- </article>
- </main>
- <GlobalFooter config={config} />
- </body>
- </PageLayout>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/pages/NotFoundPage.tsx b/vhosts/blog/nuldoc-src/pages/NotFoundPage.tsx
deleted file mode 100644
index 9631fef2..00000000
--- a/vhosts/blog/nuldoc-src/pages/NotFoundPage.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import GlobalFooter from "../components/GlobalFooter.tsx";
-import GlobalHeader from "../components/GlobalHeader.tsx";
-import PageLayout from "../components/PageLayout.tsx";
-import { Config } from "../config.ts";
-
-export default function NotFoundPage(
- config: Config,
-) {
- return (
- <PageLayout
- metaCopyrightYear={config.blog.siteCopyrightYear}
- metaDescription="リクエストされたページが見つかりません"
- metaTitle={`Page Not Found|${config.blog.siteName}`}
- config={config}
- >
- <body className="single">
- <GlobalHeader config={config} />
- <main className="main">
- <article>
- <div className="not-found">404</div>
- </article>
- </main>
- <GlobalFooter config={config} />
- </body>
- </PageLayout>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/pages/PostListPage.tsx b/vhosts/blog/nuldoc-src/pages/PostListPage.tsx
deleted file mode 100644
index 054955e6..00000000
--- a/vhosts/blog/nuldoc-src/pages/PostListPage.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import GlobalFooter from "../components/GlobalFooter.tsx";
-import GlobalHeader from "../components/GlobalHeader.tsx";
-import PageLayout from "../components/PageLayout.tsx";
-import Pagination from "../components/Pagination.tsx";
-import PostPageEntry from "../components/PostPageEntry.tsx";
-import { Config } from "../config.ts";
-import { PostPage } from "../generators/post.ts";
-
-export default function PostListPage(
- posts: PostPage[],
- config: Config,
- currentPage: number,
- totalPages: number,
-) {
- const pageTitle = "投稿一覧";
-
- const pageInfoSuffix = ` (${currentPage}ページ目)`;
- const metaTitle = `${pageTitle}${pageInfoSuffix}|${config.blog.siteName}`;
- const metaDescription = `投稿した記事の一覧${pageInfoSuffix}`;
-
- return (
- <PageLayout
- metaCopyrightYear={config.blog.siteCopyrightYear}
- metaDescription={metaDescription}
- metaTitle={metaTitle}
- metaAtomFeedHref={`https://${config.blog.fqdn}/posts/atom.xml`}
- config={config}
- >
- <body className="list">
- <GlobalHeader config={config} />
- <main className="main">
- <header className="page-header">
- <h1>{pageTitle}{pageInfoSuffix}</h1>
- </header>
-
- <Pagination
- currentPage={currentPage}
- totalPages={totalPages}
- basePath="/posts/"
- />
-
- {posts.map((post) => <PostPageEntry post={post} key={post.uuid} />)}
-
- <Pagination
- currentPage={currentPage}
- totalPages={totalPages}
- basePath="/posts/"
- />
- </main>
- <GlobalFooter config={config} />
- </body>
- </PageLayout>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/pages/PostPage.tsx b/vhosts/blog/nuldoc-src/pages/PostPage.tsx
deleted file mode 100644
index 97a24048..00000000
--- a/vhosts/blog/nuldoc-src/pages/PostPage.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import GlobalFooter from "../components/GlobalFooter.tsx";
-import GlobalHeader from "../components/GlobalHeader.tsx";
-import PageLayout from "../components/PageLayout.tsx";
-import { Config, getTagLabel } from "../config.ts";
-import { Element } from "../dom.ts";
-import { Document } from "../djot/document.ts";
-import { dateToString } from "../revision.ts";
-import { getPostPublishedDate } from "../generators/post.ts";
-
-export default function PostPage(
- doc: Document,
- config: Config,
-) {
- return (
- <PageLayout
- metaCopyrightYear={getPostPublishedDate(doc).year}
- metaDescription={doc.description}
- metaKeywords={doc.tags.map((slug) => getTagLabel(config, slug))}
- metaTitle={`${doc.title}|${config.blog.siteName}`}
- requiresSyntaxHighlight
- config={config}
- >
- <body className="single">
- <GlobalHeader config={config} />
- <main className="main">
- <article className="post-single">
- <header className="post-header">
- <h1 className="post-title">{doc.title}</h1>
- {doc.tags.length !== 0 && (
- <ul className="post-tags">
- {doc.tags.map((slug) => (
- <li className="tag">
- <a href={`/tags/${slug}/`}>{getTagLabel(config, slug)}</a>
- </li>
- ))}
- </ul>
- )}
- </header>
- <div className="post-content">
- <section id="changelog">
- <h2>
- <a href="#changelog">更新履歴</a>
- </h2>
- <ol>
- {doc.revisions.map((rev) => (
- <li className="revision">
- <time datetime={dateToString(rev.date)}>
- {dateToString(rev.date)}
- </time>
- {`: ${rev.remark}`}
- </li>
- ))}
- </ol>
- </section>
- {
- // TODO: refactor
- (doc.root.children[0] as Element).children
- }
- </div>
- </article>
- </main>
- <GlobalFooter config={config} />
- </body>
- </PageLayout>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/pages/SlideListPage.tsx b/vhosts/blog/nuldoc-src/pages/SlideListPage.tsx
deleted file mode 100644
index 3d87d492..00000000
--- a/vhosts/blog/nuldoc-src/pages/SlideListPage.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import GlobalFooter from "../components/GlobalFooter.tsx";
-import GlobalHeader from "../components/GlobalHeader.tsx";
-import PageLayout from "../components/PageLayout.tsx";
-import SlidePageEntry from "../components/SlidePageEntry.tsx";
-import { Config } from "../config.ts";
-import { dateToString } from "../revision.ts";
-import { getPostPublishedDate } from "../generators/post.ts";
-import { SlidePage } from "../generators/slide.ts";
-
-export default function SlideListPage(
- slides: SlidePage[],
- config: Config,
-) {
- const pageTitle = "スライド一覧";
-
- return (
- <PageLayout
- metaCopyrightYear={config.blog.siteCopyrightYear}
- metaDescription="登壇したイベントで使用したスライドの一覧"
- metaTitle={`${pageTitle}|${config.blog.siteName}`}
- metaAtomFeedHref={`https://${config.blog.fqdn}/slides/atom.xml`}
- config={config}
- >
- <body className="list">
- <GlobalHeader config={config} />
- <main className="main">
- <header className="page-header">
- <h1>{pageTitle}</h1>
- </header>
- {Array.from(slides).sort((a, b) => {
- const ta = dateToString(getPostPublishedDate(a));
- const tb = dateToString(getPostPublishedDate(b));
- if (ta > tb) return -1;
- if (ta < tb) return 1;
- return 0;
- }).map((slide) => <SlidePageEntry slide={slide} key={slide.uuid} />)}
- </main>
- <GlobalFooter config={config} />
- </body>
- </PageLayout>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/pages/SlidePage.tsx b/vhosts/blog/nuldoc-src/pages/SlidePage.tsx
deleted file mode 100644
index fc11072d..00000000
--- a/vhosts/blog/nuldoc-src/pages/SlidePage.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import GlobalFooter from "../components/GlobalFooter.tsx";
-import GlobalHeader from "../components/GlobalHeader.tsx";
-import PageLayout from "../components/PageLayout.tsx";
-import StaticScript from "../components/StaticScript.tsx";
-import { Config, getTagLabel } from "../config.ts";
-import { dateToString } from "../revision.ts";
-import { Slide } from "../slide/slide.ts";
-import { getPostPublishedDate } from "../generators/post.ts";
-
-export default function SlidePage(
- slide: Slide,
- config: Config,
-) {
- return (
- <PageLayout
- metaCopyrightYear={getPostPublishedDate(slide).year}
- metaDescription={slide.title}
- metaKeywords={slide.tags.map((slug) => getTagLabel(config, slug))}
- metaTitle={`${slide.event} (${slide.talkType})|${config.blog.siteName}`}
- requiresSyntaxHighlight
- config={config}
- >
- <body className="single">
- <GlobalHeader config={config} />
- <main className="main">
- <article className="post-single">
- <header className="post-header">
- <h1 className="post-title">{slide.title}</h1>
- {slide.tags.length !== 0 && (
- <ul className="post-tags">
- {slide.tags.map((slug) => (
- <li className="tag">
- <a href={`/tags/${slug}/`}>{getTagLabel(config, slug)}</a>
- </li>
- ))}
- </ul>
- )}
- </header>
- <div className="post-content">
- <section id="changelog">
- <h2>
- <a href="#changelog">更新履歴</a>
- </h2>
- <ol>
- {slide.revisions.map((rev) => (
- <li className="revision">
- <time datetime={dateToString(rev.date)}>
- {dateToString(rev.date)}
- </time>
- {`: ${rev.remark}`}
- </li>
- ))}
- </ol>
- </section>
- <canvas id="slide" data-slide-link={slide.slideLink} />
- <div>
- <button id="prev" type="button">Prev</button>
- <button id="next" type="button">Next</button>
- </div>
- <StaticScript
- fileName="/slide.js"
- type="module"
- config={config}
- />
- </div>
- </article>
- </main>
- <GlobalFooter config={config} />
- </body>
- </PageLayout>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/pages/TagListPage.tsx b/vhosts/blog/nuldoc-src/pages/TagListPage.tsx
deleted file mode 100644
index cdb83ea5..00000000
--- a/vhosts/blog/nuldoc-src/pages/TagListPage.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import GlobalFooter from "../components/GlobalFooter.tsx";
-import GlobalHeader from "../components/GlobalHeader.tsx";
-import PageLayout from "../components/PageLayout.tsx";
-import { Config } from "../config.ts";
-import { TagPage } from "../generators/tag.ts";
-
-export default function TagListPage(
- tags: TagPage[],
- config: Config,
-) {
- const pageTitle = "タグ一覧";
-
- return (
- <PageLayout
- metaCopyrightYear={config.blog.siteCopyrightYear}
- metaDescription="タグの一覧"
- metaTitle={`${pageTitle}|${config.blog.siteName}`}
- config={config}
- >
- <body className="list">
- <GlobalHeader config={config} />
- <main className="main">
- <header className="page-header">
- <h1>{pageTitle}</h1>
- </header>
- {Array.from(tags).sort((a, b) => {
- const ta = a.tagSlug;
- const tb = b.tagSlug;
- if (ta < tb) return -1;
- if (ta > tb) return 1;
- return 0;
- }).map((tag) => (
- <article className="post-entry">
- <a href={tag.href}>
- <header className="entry-header">
- <h2>{tag.tagLabel}</h2>
- </header>
- <footer className="entry-footer">
- {(() => {
- const posts = tag.numOfPosts === 0
- ? ""
- : `${tag.numOfPosts}件の記事`;
- const slides = tag.numOfSlides === 0
- ? ""
- : `${tag.numOfSlides}件のスライド`;
- return `${posts}${posts && slides ? "、" : ""}${slides}`;
- })()}
- </footer>
- </a>
- </article>
- ))}
- </main>
- <GlobalFooter config={config} />
- </body>
- </PageLayout>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/pages/TagPage.tsx b/vhosts/blog/nuldoc-src/pages/TagPage.tsx
deleted file mode 100644
index 02c484af..00000000
--- a/vhosts/blog/nuldoc-src/pages/TagPage.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import GlobalFooter from "../components/GlobalFooter.tsx";
-import GlobalHeader from "../components/GlobalHeader.tsx";
-import PageLayout from "../components/PageLayout.tsx";
-import PostPageEntry from "../components/PostPageEntry.tsx";
-import SlidePageEntry from "../components/SlidePageEntry.tsx";
-import { Config, getTagLabel } from "../config.ts";
-import { getPostPublishedDate } from "../generators/post.ts";
-import { TaggedPage } from "../generators/tagged_page.ts";
-
-export default function TagPage(
- tagSlug: string,
- pages: TaggedPage[],
- config: Config,
-) {
- const tagLabel = getTagLabel(config, tagSlug);
- const pageTitle = `タグ「${tagLabel}」一覧`;
-
- return (
- <PageLayout
- metaCopyrightYear={getPostPublishedDate(pages[pages.length - 1]).year}
- metaDescription={`タグ「${tagLabel}」のついた記事またはスライドの一覧`}
- metaKeywords={[tagLabel]}
- metaTitle={`${pageTitle}|${config.blog.siteName}`}
- metaAtomFeedHref={`https://${config.blog.fqdn}/tags/${tagSlug}/atom.xml`}
- config={config}
- >
- <body className="list">
- <GlobalHeader config={config} />
- <main className="main">
- <header className="page-header">
- <h1>{pageTitle}</h1>
- </header>
- {pages.map((page) =>
- "event" in page
- ? <SlidePageEntry slide={page} key={page.uuid} />
- : <PostPageEntry post={page} key={page.uuid} />
- )}
- </main>
- <GlobalFooter config={config} />
- </body>
- </PageLayout>
- );
-}
diff --git a/vhosts/blog/nuldoc-src/render.ts b/vhosts/blog/nuldoc-src/render.ts
deleted file mode 100644
index fbad25ab..00000000
--- a/vhosts/blog/nuldoc-src/render.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Node } from "./dom.ts";
-import { renderHtml } from "./renderers/html.ts";
-import { renderXml } from "./renderers/xml.ts";
-
-export type RendererType = "html" | "xml";
-
-export function render(root: Node, renderer: RendererType): string {
- if (renderer === "html") {
- return renderHtml(root);
- } else {
- return renderXml(root);
- }
-}
diff --git a/vhosts/blog/nuldoc-src/renderers/html.ts b/vhosts/blog/nuldoc-src/renderers/html.ts
deleted file mode 100644
index 84b3ebaa..00000000
--- a/vhosts/blog/nuldoc-src/renderers/html.ts
+++ /dev/null
@@ -1,310 +0,0 @@
-import { Element, forEachChild, Node, Text } from "../dom.ts";
-import { NuldocError } from "../errors.ts";
-
-export function renderHtml(root: Node): string {
- return `<!DOCTYPE html>\n` + nodeToHtmlText(root, {
- indentLevel: 0,
- isInPre: false,
- });
-}
-
-type Context = {
- indentLevel: number;
- isInPre: boolean;
-};
-
-type Dtd = { type: "block" | "inline"; self_closing?: boolean };
-
-function getDtd(name: string): Dtd {
- switch (name) {
- case "a":
- return { type: "inline" };
- case "article":
- return { type: "block" };
- case "blockquote":
- return { type: "block" };
- case "body":
- return { type: "block" };
- case "br":
- return { type: "block", self_closing: true };
- case "button":
- return { type: "block" };
- case "canvas":
- return { type: "block" };
- case "caption":
- return { type: "block" };
- case "code":
- return { type: "inline" };
- case "del":
- return { type: "block" };
- case "div":
- return { type: "block" };
- case "em":
- return { type: "inline" };
- case "footer":
- return { type: "block" };
- case "h1":
- return { type: "inline" };
- case "h2":
- return { type: "inline" };
- case "h3":
- return { type: "inline" };
- case "h4":
- return { type: "inline" };
- case "h5":
- return { type: "inline" };
- case "h6":
- return { type: "inline" };
- case "head":
- return { type: "block" };
- case "header":
- return { type: "block" };
- case "hr":
- return { type: "block", self_closing: true };
- case "html":
- return { type: "block" };
- case "i":
- return { type: "inline" };
- case "li":
- return { type: "block" };
- case "link":
- return { type: "block", self_closing: true };
- case "img":
- return { type: "inline", self_closing: true };
- case "ins":
- return { type: "block" };
- case "main":
- return { type: "block" };
- case "mark":
- return { type: "inline" };
- case "meta":
- return { type: "block", self_closing: true };
- case "nav":
- return { type: "block" };
- case "noscript":
- return { type: "block" };
- case "ol":
- return { type: "block" };
- case "p":
- return { type: "block" };
- case "pre":
- return { type: "block" };
- case "script":
- return { type: "block" };
- case "section":
- return { type: "block" };
- case "span":
- return { type: "inline" };
- case "strong":
- return { type: "inline" };
- case "sub":
- return { type: "inline" };
- case "sup":
- return { type: "inline" };
- case "table":
- return { type: "block" };
- case "tbody":
- return { type: "block" };
- case "td": // TODO
- return { type: "block" };
- case "tfoot":
- return { type: "block" };
- case "th":
- return { type: "block" };
- case "thead":
- return { type: "block" };
- case "time":
- return { type: "inline" };
- case "title": // TODO
- return { type: "inline" };
- case "tr":
- return { type: "block" };
- case "ul":
- return { type: "block" };
- default:
- throw new NuldocError(`[html.write] Unknown element name: ${name}`);
- }
-}
-
-function isInlineNode(n: Node): boolean {
- if (n.kind === "text") {
- return true;
- }
- if (n.name !== "a") {
- return getDtd(n.name).type === "inline";
- }
-
- // a tag: check if all children are inline elements.
- let allInline = true;
- forEachChild(n, (c) => allInline &&= isInlineNode(c));
- return allInline;
-}
-
-function isBlockNode(n: Node): boolean {
- return !isInlineNode(n);
-}
-
-function nodeToHtmlText(n: Node, ctx: Context): string {
- if (n.kind === "text") {
- if (n.raw) {
- return n.content;
- } else {
- return textNodeToHtmlText(n, ctx);
- }
- } else {
- return elementNodeToHtmlText(n, ctx);
- }
-}
-
-function textNodeToHtmlText(t: Text, ctx: Context): string {
- const s = encodeSpecialCharacters(t.content);
- if (ctx.isInPre) return s;
-
- return s.replaceAll(/\n */g, (_match, offset, subject) => {
- const last_char = subject[offset - 1];
- if (last_char === "。" || last_char === "、") {
- // 日本語で改行するときはスペースを入れない
- return "";
- } else {
- return " ";
- }
- });
-}
-
-function encodeSpecialCharacters(s: string): string {
- return s.replaceAll(/&(?!\w+;)/g, "&amp;")
- .replaceAll(/</g, "&lt;")
- .replaceAll(/>/g, "&gt;")
- .replaceAll(/'/g, "&apos;")
- .replaceAll(/"/g, "&quot;");
-}
-
-function elementNodeToHtmlText(e: Element, ctx: Context): string {
- const dtd = getDtd(e.name);
-
- let s = "";
-
- if (isBlockNode(e)) {
- s += indent(ctx);
- }
- s += `<${e.name}`;
- const attributes = getElementAttributes(e);
- if (attributes.length > 0) {
- s += " ";
- for (let i = 0; i < attributes.length; i++) {
- const [name, value] = attributes[i];
- if (name === "defer" && value === "true") {
- // TODO
- s += "defer";
- } else {
- s += `${name === "className" ? "class" : name}="${
- encodeSpecialCharacters(value)
- }"`;
- }
- if (i !== attributes.length - 1) {
- s += " ";
- }
- }
- }
- s += ">";
- if (isBlockNode(e) && e.name !== "pre") {
- s += "\n";
- }
-
- ctx.indentLevel += 1;
-
- let prevChild: Node | null = null;
- if (e.name === "pre") {
- ctx.isInPre = true;
- }
- forEachChild(e, (c) => {
- if (isBlockNode(e) && !ctx.isInPre) {
- if (isInlineNode(c)) {
- if (needsIndent(prevChild)) {
- s += indent(ctx);
- }
- } else {
- if (needsLineBreak(prevChild)) {
- s += "\n";
- }
- }
- }
- s += nodeToHtmlText(c, ctx);
- prevChild = c;
- });
- if (e.name === "pre") {
- ctx.isInPre = false;
- }
-
- ctx.indentLevel -= 1;
- if (!dtd.self_closing) {
- if (e.name !== "pre") {
- if (isBlockNode(e)) {
- if (needsLineBreak(prevChild)) {
- s += "\n";
- }
- s += indent(ctx);
- }
- }
- s += `</${e.name}>`;
- if (isBlockNode(e)) {
- s += "\n";
- }
- }
- return s;
-}
-
-function indent(ctx: Context): string {
- return " ".repeat(ctx.indentLevel);
-}
-
-function getElementAttributes(e: Element): [string, string][] {
- return [...e.attributes.entries()]
- .filter((a) => !a[0].startsWith("--"))
- .filter((a) => a[1] !== undefined)
- .sort(
- (a, b) => {
- // Special rules:
- if (e.name === "meta") {
- if (a[0] === "content" && b[0] === "name") {
- return 1;
- }
- if (a[0] === "name" && b[0] === "content") {
- return -1;
- }
- if (a[0] === "content" && b[0] === "property") {
- return 1;
- }
- if (a[0] === "property" && b[0] === "content") {
- return -1;
- }
- }
- if (e.name === "link") {
- if (a[0] === "href" && b[0] === "rel") {
- return 1;
- }
- if (a[0] === "rel" && b[0] === "href") {
- return -1;
- }
- if (a[0] === "href" && b[0] === "type") {
- return 1;
- }
- if (a[0] === "type" && b[0] === "href") {
- return -1;
- }
- }
- // General rules:
- if (a[0] > b[0]) return 1;
- else if (a[0] < b[0]) return -1;
- else return 0;
- },
- );
-}
-
-function needsIndent(prevChild: Node | null): boolean {
- return !prevChild || isBlockNode(prevChild);
-}
-
-function needsLineBreak(prevChild: Node | null): boolean {
- return !needsIndent(prevChild);
-}
diff --git a/vhosts/blog/nuldoc-src/renderers/xml.ts b/vhosts/blog/nuldoc-src/renderers/xml.ts
deleted file mode 100644
index 77cc1574..00000000
--- a/vhosts/blog/nuldoc-src/renderers/xml.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import { Element, forEachChild, Node, Text } from "../dom.ts";
-
-export function renderXml(root: Node): string {
- return `<?xml version="1.0" encoding="utf-8"?>\n` + nodeToXmlText(root, {
- indentLevel: 0,
- });
-}
-
-type Context = {
- indentLevel: number;
-};
-
-type Dtd = { type: "block" | "inline" };
-
-function getDtd(name: string): Dtd {
- switch (name) {
- case "feed":
- case "entry":
- case "author":
- return { type: "block" };
- default:
- return { type: "inline" };
- }
-}
-
-function isInlineNode(n: Node): boolean {
- if (n.kind === "text") {
- return true;
- }
- return getDtd(n.name).type === "inline";
-}
-
-function isBlockNode(n: Node): boolean {
- return !isInlineNode(n);
-}
-
-function nodeToXmlText(n: Node, ctx: Context): string {
- if (n.kind === "text") {
- if (n.raw) {
- return n.content;
- } else {
- return textNodeToXmlText(n);
- }
- } else {
- return elementNodeToXmlText(n, ctx);
- }
-}
-
-function textNodeToXmlText(t: Text): string {
- const s = encodeSpecialCharacters(t.content);
-
- // TODO: 日本語で改行するときはスペースを入れない
- return s.replaceAll(/\n */g, " ");
-}
-
-function encodeSpecialCharacters(s: string): string {
- return s.replaceAll(/&(?!\w+;)/g, "&amp;")
- .replaceAll(/</g, "&lt;")
- .replaceAll(/>/g, "&gt;")
- .replaceAll(/'/g, "&apos;")
- .replaceAll(/"/g, "&quot;");
-}
-
-function elementNodeToXmlText(e: Element, ctx: Context): string {
- let s = "";
-
- s += indent(ctx);
- s += `<${e.name}`;
- const attributes = getElementAttributes(e);
- if (attributes.length > 0) {
- s += " ";
- for (let i = 0; i < attributes.length; i++) {
- const [name, value] = attributes[i];
- s += `${name}="${encodeSpecialCharacters(value)}"`;
- if (i !== attributes.length - 1) {
- s += " ";
- }
- }
- }
- s += ">";
- if (isBlockNode(e)) {
- s += "\n";
- }
- ctx.indentLevel += 1;
-
- forEachChild(e, (c) => {
- s += nodeToXmlText(c, ctx);
- });
-
- ctx.indentLevel -= 1;
- if (isBlockNode(e)) {
- s += indent(ctx);
- }
- s += `</${e.name}>`;
- s += "\n";
-
- return s;
-}
-
-function indent(ctx: Context): string {
- return " ".repeat(ctx.indentLevel);
-}
-
-function getElementAttributes(e: Element): [string, string][] {
- return [...e.attributes.entries()]
- .filter((a) => !a[0].startsWith("--"))
- .sort(
- (a, b) => {
- // Special rules:
- if (e.name === "link") {
- if (a[0] === "href" && b[0] === "rel") {
- return 1;
- }
- if (a[0] === "rel" && b[0] === "href") {
- return -1;
- }
- if (a[0] === "href" && b[0] === "type") {
- return 1;
- }
- if (a[0] === "type" && b[0] === "href") {
- return -1;
- }
- }
- // General rules:
- if (a[0] > b[0]) return 1;
- else if (a[0] < b[0]) return -1;
- else return 0;
- },
- );
-}
diff --git a/vhosts/blog/nuldoc-src/revision.ts b/vhosts/blog/nuldoc-src/revision.ts
deleted file mode 100644
index a22b6bc4..00000000
--- a/vhosts/blog/nuldoc-src/revision.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-export type Date = {
- year: number;
- month: number;
- day: number;
-};
-
-export function stringToDate(s: string): Date {
- const match = s.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (match === null) {
- throw new Error();
- }
- const [_, y, m, d] = match;
- return { year: parseInt(y), month: parseInt(m), day: parseInt(d) };
-}
-
-export function dateToString(date: Date): string {
- const y = `${date.year}`.padStart(4, "0");
- const m = `${date.month}`.padStart(2, "0");
- const d = `${date.day}`.padStart(2, "0");
- return `${y}-${m}-${d}`;
-}
-
-export function dateToRfc3339String(date: Date): string {
- // 2021-01-01T12:00:00+00:00
- // TODO: currently, time part is fixed to 00:00:00.
- const y = `${date.year}`.padStart(4, "0");
- const m = `${date.month}`.padStart(2, "0");
- const d = `${date.day}`.padStart(2, "0");
- return `${y}-${m}-${d}T00:00:00+09:00`;
-}
-
-export type Revision = {
- number: number;
- date: Date;
- remark: string;
- isInternal: boolean;
-};
diff --git a/vhosts/blog/nuldoc-src/slide/parse.ts b/vhosts/blog/nuldoc-src/slide/parse.ts
deleted file mode 100644
index c5a89675..00000000
--- a/vhosts/blog/nuldoc-src/slide/parse.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { parse as parseToml } from "@std/toml";
-import {
- createNewSlideFromMetadata,
- Slide,
- SlideMetadataSchema,
-} from "./slide.ts";
-
-export async function parseSlideFile(filePath: string): Promise<Slide> {
- try {
- const root = SlideMetadataSchema.parse(
- parseToml(await Deno.readTextFile(filePath)),
- );
- return createNewSlideFromMetadata(root, filePath);
- } catch (e) {
- if (e instanceof Error) {
- e.message = `${e.message} in ${filePath}`;
- }
- throw e;
- }
-}
diff --git a/vhosts/blog/nuldoc-src/slide/slide.ts b/vhosts/blog/nuldoc-src/slide/slide.ts
deleted file mode 100644
index 8fe99eab..00000000
--- a/vhosts/blog/nuldoc-src/slide/slide.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { SlideError } from "../errors.ts";
-import { Revision, stringToDate } from "../revision.ts";
-import { z } from "zod/mod.ts";
-
-export const SlideMetadataSchema = z.object({
- slide: z.object({
- uuid: z.string(),
- title: z.string(),
- event: z.string(),
- talkType: z.string(),
- link: z.string(),
- tags: z.array(z.string()),
- revisions: z.array(z.object({
- date: z.string(),
- remark: z.string(),
- isInternal: z.boolean().optional(),
- })),
- }),
-});
-
-export type SlideMetadata = z.infer<typeof SlideMetadataSchema>;
-
-export type Slide = {
- sourceFilePath: string;
- uuid: string;
- title: string;
- event: string;
- talkType: string;
- slideLink: string;
- tags: string[];
- revisions: Revision[];
-};
-
-export function createNewSlideFromMetadata(
- { slide }: SlideMetadata,
- sourceFilePath: string,
-): Slide {
- const revisions = slide.revisions.map(
- (rev, i) => {
- const date = rev.date;
- const remark = rev.remark;
- const isInternal = rev.isInternal ?? false;
- return {
- number: i + 1,
- date: stringToDate(date),
- remark,
- isInternal,
- };
- },
- );
- if (revisions.length === 0) {
- throw new SlideError(
- `[slide.new] 'slide.revisions' field is empty`,
- );
- }
-
- return {
- sourceFilePath: sourceFilePath,
- uuid: slide.uuid,
- title: slide.title,
- event: slide.event,
- talkType: slide.talkType,
- slideLink: slide.link,
- tags: slide.tags,
- revisions: revisions,
- };
-}