From d1014de68415df8f0a5dc3389332e086119c6198 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Thu, 27 Nov 2025 04:02:06 +0900 Subject: refactor(nuldoc): eliminate JSX --- services/nuldoc/deno.jsonc | 9 +- services/nuldoc/nuldoc-src/commands/build.ts | 6 +- .../nuldoc-src/components/AboutGlobalHeader.ts | 18 +++ .../nuldoc-src/components/AboutGlobalHeader.tsx | 13 -- .../nuldoc-src/components/BlogGlobalHeader.ts | 34 +++++ .../nuldoc-src/components/BlogGlobalHeader.tsx | 29 ---- .../nuldoc-src/components/DefaultGlobalHeader.ts | 18 +++ .../nuldoc-src/components/DefaultGlobalHeader.tsx | 13 -- .../nuldoc/nuldoc-src/components/GlobalFooter.ts | 10 ++ .../nuldoc/nuldoc-src/components/GlobalFooter.tsx | 9 -- .../nuldoc/nuldoc-src/components/PageLayout.ts | 76 ++++++++++ .../nuldoc/nuldoc-src/components/PageLayout.tsx | 65 --------- .../nuldoc/nuldoc-src/components/Pagination.ts | 98 +++++++++++++ .../nuldoc/nuldoc-src/components/Pagination.tsx | 104 -------------- .../nuldoc/nuldoc-src/components/PostPageEntry.ts | 49 +++++++ .../nuldoc/nuldoc-src/components/PostPageEntry.tsx | 46 ------ .../nuldoc/nuldoc-src/components/SlidePageEntry.ts | 49 +++++++ .../nuldoc-src/components/SlidePageEntry.tsx | 46 ------ .../nuldoc-src/components/SlidesGlobalHeader.ts | 33 +++++ .../nuldoc-src/components/SlidesGlobalHeader.tsx | 26 ---- .../nuldoc/nuldoc-src/components/StaticScript.ts | 20 +++ .../nuldoc/nuldoc-src/components/StaticScript.tsx | 18 --- .../nuldoc-src/components/StaticStylesheet.ts | 12 ++ .../nuldoc-src/components/StaticStylesheet.tsx | 11 -- .../nuldoc-src/components/TableOfContents.ts | 34 +++++ .../nuldoc-src/components/TableOfContents.tsx | 33 ----- services/nuldoc/nuldoc-src/components/TagList.ts | 17 +++ services/nuldoc/nuldoc-src/components/TagList.tsx | 18 --- services/nuldoc/nuldoc-src/dom.ts | 23 ++- services/nuldoc/nuldoc-src/generators/about.ts | 7 +- services/nuldoc/nuldoc-src/generators/atom.ts | 9 +- services/nuldoc/nuldoc-src/generators/home.ts | 7 +- services/nuldoc/nuldoc-src/generators/not_found.ts | 7 +- services/nuldoc/nuldoc-src/generators/post.ts | 7 +- services/nuldoc/nuldoc-src/generators/post_list.ts | 12 +- services/nuldoc/nuldoc-src/generators/slide.ts | 7 +- .../nuldoc/nuldoc-src/generators/slide_list.ts | 7 +- services/nuldoc/nuldoc-src/generators/tag.ts | 7 +- services/nuldoc/nuldoc-src/generators/tag_list.ts | 7 +- services/nuldoc/nuldoc-src/jsx/jsx-runtime.ts | 27 ---- services/nuldoc/nuldoc-src/jsx/render.ts | 41 ------ services/nuldoc/nuldoc-src/jsx/types.d.ts | 84 ----------- services/nuldoc/nuldoc-src/pages/AboutPage.ts | 160 +++++++++++++++++++++ services/nuldoc/nuldoc-src/pages/AboutPage.tsx | 113 --------------- services/nuldoc/nuldoc-src/pages/AtomPage.ts | 27 ++++ services/nuldoc/nuldoc-src/pages/AtomPage.tsx | 26 ---- services/nuldoc/nuldoc-src/pages/HomePage.ts | 78 ++++++++++ services/nuldoc/nuldoc-src/pages/HomePage.tsx | 54 ------- services/nuldoc/nuldoc-src/pages/NotFoundPage.ts | 40 ++++++ services/nuldoc/nuldoc-src/pages/NotFoundPage.tsx | 38 ----- services/nuldoc/nuldoc-src/pages/PostListPage.ts | 49 +++++++ services/nuldoc/nuldoc-src/pages/PostListPage.tsx | 58 -------- services/nuldoc/nuldoc-src/pages/PostPage.ts | 90 ++++++++++++ services/nuldoc/nuldoc-src/pages/PostPage.tsx | 71 --------- services/nuldoc/nuldoc-src/pages/SlideListPage.ts | 45 ++++++ services/nuldoc/nuldoc-src/pages/SlideListPage.tsx | 45 ------ services/nuldoc/nuldoc-src/pages/SlidePage.ts | 98 +++++++++++++ services/nuldoc/nuldoc-src/pages/SlidePage.tsx | 73 ---------- services/nuldoc/nuldoc-src/pages/TagListPage.ts | 70 +++++++++ services/nuldoc/nuldoc-src/pages/TagListPage.tsx | 62 -------- services/nuldoc/nuldoc-src/pages/TagPage.ts | 50 +++++++ services/nuldoc/nuldoc-src/pages/TagPage.tsx | 50 ------- 62 files changed, 1222 insertions(+), 1241 deletions(-) create mode 100644 services/nuldoc/nuldoc-src/components/AboutGlobalHeader.ts delete mode 100644 services/nuldoc/nuldoc-src/components/AboutGlobalHeader.tsx create mode 100644 services/nuldoc/nuldoc-src/components/BlogGlobalHeader.ts delete mode 100644 services/nuldoc/nuldoc-src/components/BlogGlobalHeader.tsx create mode 100644 services/nuldoc/nuldoc-src/components/DefaultGlobalHeader.ts delete mode 100644 services/nuldoc/nuldoc-src/components/DefaultGlobalHeader.tsx create mode 100644 services/nuldoc/nuldoc-src/components/GlobalFooter.ts delete mode 100644 services/nuldoc/nuldoc-src/components/GlobalFooter.tsx create mode 100644 services/nuldoc/nuldoc-src/components/PageLayout.ts delete mode 100644 services/nuldoc/nuldoc-src/components/PageLayout.tsx create mode 100644 services/nuldoc/nuldoc-src/components/Pagination.ts delete mode 100644 services/nuldoc/nuldoc-src/components/Pagination.tsx create mode 100644 services/nuldoc/nuldoc-src/components/PostPageEntry.ts delete mode 100644 services/nuldoc/nuldoc-src/components/PostPageEntry.tsx create mode 100644 services/nuldoc/nuldoc-src/components/SlidePageEntry.ts delete mode 100644 services/nuldoc/nuldoc-src/components/SlidePageEntry.tsx create mode 100644 services/nuldoc/nuldoc-src/components/SlidesGlobalHeader.ts delete mode 100644 services/nuldoc/nuldoc-src/components/SlidesGlobalHeader.tsx create mode 100644 services/nuldoc/nuldoc-src/components/StaticScript.ts delete mode 100644 services/nuldoc/nuldoc-src/components/StaticScript.tsx create mode 100644 services/nuldoc/nuldoc-src/components/StaticStylesheet.ts delete mode 100644 services/nuldoc/nuldoc-src/components/StaticStylesheet.tsx create mode 100644 services/nuldoc/nuldoc-src/components/TableOfContents.ts delete mode 100644 services/nuldoc/nuldoc-src/components/TableOfContents.tsx create mode 100644 services/nuldoc/nuldoc-src/components/TagList.ts delete mode 100644 services/nuldoc/nuldoc-src/components/TagList.tsx delete mode 100644 services/nuldoc/nuldoc-src/jsx/jsx-runtime.ts delete mode 100644 services/nuldoc/nuldoc-src/jsx/render.ts delete mode 100644 services/nuldoc/nuldoc-src/jsx/types.d.ts create mode 100644 services/nuldoc/nuldoc-src/pages/AboutPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/AboutPage.tsx create mode 100644 services/nuldoc/nuldoc-src/pages/AtomPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/AtomPage.tsx create mode 100644 services/nuldoc/nuldoc-src/pages/HomePage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/HomePage.tsx create mode 100644 services/nuldoc/nuldoc-src/pages/NotFoundPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/NotFoundPage.tsx create mode 100644 services/nuldoc/nuldoc-src/pages/PostListPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/PostListPage.tsx create mode 100644 services/nuldoc/nuldoc-src/pages/PostPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/PostPage.tsx create mode 100644 services/nuldoc/nuldoc-src/pages/SlideListPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/SlideListPage.tsx create mode 100644 services/nuldoc/nuldoc-src/pages/SlidePage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/SlidePage.tsx create mode 100644 services/nuldoc/nuldoc-src/pages/TagListPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/TagListPage.tsx create mode 100644 services/nuldoc/nuldoc-src/pages/TagPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/TagPage.tsx diff --git a/services/nuldoc/deno.jsonc b/services/nuldoc/deno.jsonc index 5ae2eef..aff8a05 100644 --- a/services/nuldoc/deno.jsonc +++ b/services/nuldoc/deno.jsonc @@ -9,14 +9,7 @@ "@std/toml": "jsr:@std/toml@^1.0.8", "checksum/": "https://deno.land/x/checksum@1.4.0/", "shiki": "npm:shiki@^3.7.0", - "zod/": "https://deno.land/x/zod@v3.24.2/", - "myjsx/jsx-runtime": "./nuldoc-src/jsx/jsx-runtime.ts", - "myjsx-types/jsx-runtime": "./nuldoc-src/jsx/types.d.ts", - }, - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "myjsx", - "jsxImportSourceTypes": "myjsx-types", + "zod/": "https://deno.land/x/zod@v3.24.2/" }, "permissions": { "default": { diff --git a/services/nuldoc/nuldoc-src/commands/build.ts b/services/nuldoc/nuldoc-src/commands/build.ts index 3022ee5..5efff55 100644 --- a/services/nuldoc/nuldoc-src/commands/build.ts +++ b/services/nuldoc/nuldoc-src/commands/build.ts @@ -90,7 +90,7 @@ async function buildPostListPage(posts: PostPage[], config: Config) { await writePage(page, config); } - const postFeedPage = await generateFeedPageFromEntries( + const postFeedPage = generateFeedPageFromEntries( "/posts/", "posts", `投稿一覧|${config.sites.blog.siteName}`, @@ -136,7 +136,7 @@ async function parseSlides( async function buildSlideListPage(slides: SlidePage[], config: Config) { const slideListPage = await generateSlideListPage(slides, config); await writePage(slideListPage, config); - const slideFeedPage = await generateFeedPageFromEntries( + const slideFeedPage = generateFeedPageFromEntries( slideListPage.href, "slides", `スライド一覧|${config.sites.slides.siteName}`, @@ -175,7 +175,7 @@ async function buildTagPages( for (const [tag, pages] of tagsAndPages) { const tagPage = await generateTagPage(tag, pages, site, config); await writePage(tagPage, config); - const tagFeedPage = await generateFeedPageFromEntries( + const tagFeedPage = generateFeedPageFromEntries( tagPage.href, `tag-${tag}`, `タグ「${getTagLabel(config, tag)}」一覧|${config.sites[site].siteName}`, diff --git a/services/nuldoc/nuldoc-src/components/AboutGlobalHeader.ts b/services/nuldoc/nuldoc-src/components/AboutGlobalHeader.ts new file mode 100644 index 0000000..b6ef84c --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/AboutGlobalHeader.ts @@ -0,0 +1,18 @@ +import { Config } from "../config.ts"; +import { elem, Element } from "../dom.ts"; + +export default function GlobalHeader({ config }: { config: Config }): Element { + return elem( + "header", + { class: "header" }, + elem( + "div", + { class: "site-logo" }, + elem( + "a", + { href: `https://${config.sites.default.fqdn}/` }, + "nsfisis.dev", + ), + ), + ); +} diff --git a/services/nuldoc/nuldoc-src/components/AboutGlobalHeader.tsx b/services/nuldoc/nuldoc-src/components/AboutGlobalHeader.tsx deleted file mode 100644 index 2df7296..0000000 --- a/services/nuldoc/nuldoc-src/components/AboutGlobalHeader.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Config } from "../config.ts"; - -export default function GlobalHeader({ config }: { config: Config }) { - return ( -
-
- - nsfisis.dev - -
-
- ); -} diff --git a/services/nuldoc/nuldoc-src/components/BlogGlobalHeader.ts b/services/nuldoc/nuldoc-src/components/BlogGlobalHeader.ts new file mode 100644 index 0000000..034c2ab --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/BlogGlobalHeader.ts @@ -0,0 +1,34 @@ +import { Config } from "../config.ts"; +import { elem, Element } from "../dom.ts"; + +export default function GlobalHeader({ config }: { config: Config }): Element { + return elem( + "header", + { class: "header" }, + elem( + "div", + { class: "site-logo" }, + elem( + "a", + { href: `https://${config.sites.default.fqdn}/` }, + "nsfisis.dev", + ), + ), + elem("div", { class: "site-name" }, config.sites.blog.siteName), + elem( + "nav", + { class: "nav" }, + elem( + "ul", + {}, + elem( + "li", + {}, + elem("a", { href: `https://${config.sites.about.fqdn}/` }, "About"), + ), + elem("li", {}, elem("a", { href: "/posts/" }, "Posts")), + elem("li", {}, elem("a", { href: "/tags/" }, "Tags")), + ), + ), + ); +} diff --git a/services/nuldoc/nuldoc-src/components/BlogGlobalHeader.tsx b/services/nuldoc/nuldoc-src/components/BlogGlobalHeader.tsx deleted file mode 100644 index 1f7fe6e..0000000 --- a/services/nuldoc/nuldoc-src/components/BlogGlobalHeader.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Config } from "../config.ts"; - -export default function GlobalHeader({ config }: { config: Config }) { - return ( -
-
- - nsfisis.dev - -
-
- {config.sites.blog.siteName} -
- -
- ); -} diff --git a/services/nuldoc/nuldoc-src/components/DefaultGlobalHeader.ts b/services/nuldoc/nuldoc-src/components/DefaultGlobalHeader.ts new file mode 100644 index 0000000..b6ef84c --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/DefaultGlobalHeader.ts @@ -0,0 +1,18 @@ +import { Config } from "../config.ts"; +import { elem, Element } from "../dom.ts"; + +export default function GlobalHeader({ config }: { config: Config }): Element { + return elem( + "header", + { class: "header" }, + elem( + "div", + { class: "site-logo" }, + elem( + "a", + { href: `https://${config.sites.default.fqdn}/` }, + "nsfisis.dev", + ), + ), + ); +} diff --git a/services/nuldoc/nuldoc-src/components/DefaultGlobalHeader.tsx b/services/nuldoc/nuldoc-src/components/DefaultGlobalHeader.tsx deleted file mode 100644 index 2df7296..0000000 --- a/services/nuldoc/nuldoc-src/components/DefaultGlobalHeader.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Config } from "../config.ts"; - -export default function GlobalHeader({ config }: { config: Config }) { - return ( -
-
- - nsfisis.dev - -
-
- ); -} diff --git a/services/nuldoc/nuldoc-src/components/GlobalFooter.ts b/services/nuldoc/nuldoc-src/components/GlobalFooter.ts new file mode 100644 index 0000000..835d73f --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/GlobalFooter.ts @@ -0,0 +1,10 @@ +import { Config } from "../config.ts"; +import { elem, Element } from "../dom.ts"; + +export default function GlobalFooter({ config }: { config: Config }): Element { + return elem( + "footer", + { class: "footer" }, + `© ${config.site.copyrightYear} ${config.site.author}`, + ); +} diff --git a/services/nuldoc/nuldoc-src/components/GlobalFooter.tsx b/services/nuldoc/nuldoc-src/components/GlobalFooter.tsx deleted file mode 100644 index 9374aa7..0000000 --- a/services/nuldoc/nuldoc-src/components/GlobalFooter.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Config } from "../config.ts"; - -export default function GlobalFooter({ config }: { config: Config }) { - return ( - - ); -} diff --git a/services/nuldoc/nuldoc-src/components/PageLayout.ts b/services/nuldoc/nuldoc-src/components/PageLayout.ts new file mode 100644 index 0000000..19a8e86 --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/PageLayout.ts @@ -0,0 +1,76 @@ +import { Config } from "../config.ts"; +import { elem, Element, Node } from "../dom.ts"; +import StaticStylesheet from "./StaticStylesheet.ts"; + +type Props = { + metaCopyrightYear: number; + metaDescription: string; + metaKeywords?: string[]; + metaTitle: string; + metaAtomFeedHref?: string; + requiresSyntaxHighlight?: boolean; + site: "default" | "about" | "blog" | "slides"; + config: Config; + children: Node; +}; + +export default async function PageLayout( + { + metaCopyrightYear, + metaDescription, + metaKeywords, + metaTitle, + metaAtomFeedHref, + requiresSyntaxHighlight: _, + site, + config, + children, + }: Props, +): Promise { + return elem( + "html", + { lang: "ja-JP" }, + elem( + "head", + {}, + elem("meta", { charset: "UTF-8" }), + elem("meta", { + name: "viewport", + content: "width=device-width, initial-scale=1.0", + }), + elem("meta", { name: "author", content: config.site.author }), + elem("meta", { + name: "copyright", + content: `© ${metaCopyrightYear} ${config.site.author}`, + }), + elem("meta", { name: "description", content: metaDescription }), + metaKeywords && metaKeywords.length !== 0 + ? elem("meta", { name: "keywords", content: metaKeywords.join(",") }) + : null, + elem("meta", { property: "og:type", content: "article" }), + elem("meta", { property: "og:title", content: metaTitle }), + elem("meta", { property: "og:description", content: metaDescription }), + elem("meta", { + property: "og:site_name", + content: config.sites[site].siteName, + }), + elem("meta", { property: "og:locale", content: "ja_JP" }), + elem("meta", { name: "Hatena::Bookmark", content: "nocomment" }), + metaAtomFeedHref + ? elem("link", { + rel: "alternate", + href: metaAtomFeedHref, + type: "application/atom+xml", + }) + : null, + elem("link", { + rel: "icon", + href: "/favicon.svg", + type: "image/svg+xml", + }), + elem("title", {}, metaTitle), + await StaticStylesheet({ fileName: "/style.css", config }), + ), + children, + ); +} diff --git a/services/nuldoc/nuldoc-src/components/PageLayout.tsx b/services/nuldoc/nuldoc-src/components/PageLayout.tsx deleted file mode 100644 index b32f229..0000000 --- a/services/nuldoc/nuldoc-src/components/PageLayout.tsx +++ /dev/null @@ -1,65 +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; - site: "default" | "about" | "blog" | "slides"; - config: Config; - children: JSXNode; -}; - -export default function PageLayout( - { - metaCopyrightYear, - metaDescription, - metaKeywords, - metaTitle, - metaAtomFeedHref, - requiresSyntaxHighlight: _, - site, - config, - children, - }: Props, -) { - return ( - - - - - - - - {metaKeywords && metaKeywords.length !== 0 && - } - - - - - - {/* https://b.hatena.ne.jp/help/entry/nocomment */} - - {metaAtomFeedHref && - ( - - )} - - {metaTitle} - - - {children} - - ); -} diff --git a/services/nuldoc/nuldoc-src/components/Pagination.ts b/services/nuldoc/nuldoc-src/components/Pagination.ts new file mode 100644 index 0000000..62e796b --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/Pagination.ts @@ -0,0 +1,98 @@ +import { elem, Element } from "../dom.ts"; + +type Props = { + currentPage: number; + totalPages: number; + basePath: string; +}; + +export default function Pagination( + { currentPage, totalPages, basePath }: Props, +): Element { + if (totalPages <= 1) { + return elem("div", {}); + } + + const pages = generatePageNumbers(currentPage, totalPages); + + return elem( + "nav", + { class: "pagination" }, + elem( + "div", + { class: "pagination-prev" }, + currentPage > 1 + ? elem("a", { href: pageUrlAt(basePath, currentPage - 1) }, "前へ") + : null, + ), + ...pages.map((page) => { + if (page === "...") { + return elem("div", { class: "pagination-elipsis" }, "…"); + } else if (page === currentPage) { + return elem( + "div", + { class: "pagination-page pagination-page-current" }, + elem("span", {}, String(page)), + ); + } else { + return elem( + "div", + { class: "pagination-page" }, + elem("a", { href: pageUrlAt(basePath, page) }, String(page)), + ); + } + }), + elem( + "div", + { class: "pagination-next" }, + currentPage < totalPages + ? elem("a", { href: pageUrlAt(basePath, currentPage + 1) }, "次へ") + : null, + ), + ); +} + +type PageItem = number | "..."; + +/** + * Generates page numbers for pagination display. + * + * - Always show the first page + * - Always show the last page + * - Always show the current page + * - Always show the page before and after the current page + * - If there's only one page gap between displayed pages, fill it + * - If there are two or more pages gap between displayed pages, show ellipsis + */ +function generatePageNumbers( + currentPage: number, + totalPages: number, +): PageItem[] { + const pages = new Set(); + pages.add(1); + pages.add(Math.max(1, currentPage - 1)); + pages.add(currentPage); + pages.add(Math.min(totalPages, currentPage + 1)); + pages.add(totalPages); + + const sorted = Array.from(pages).sort((a, b) => a - b); + + const result: PageItem[] = []; + for (let i = 0; i < sorted.length; i++) { + if (i > 0) { + const gap = sorted[i] - sorted[i - 1]; + if (gap === 2) { + result.push(sorted[i - 1] + 1); + } else if (gap > 2) { + result.push("..."); + } + } + result.push(sorted[i]); + } + + return result; +} + +function pageUrlAt(basePath: string, page: number): string { + return page === 1 ? basePath : `${basePath}${page}/`; +} diff --git a/services/nuldoc/nuldoc-src/components/Pagination.tsx b/services/nuldoc/nuldoc-src/components/Pagination.tsx deleted file mode 100644 index 84752c5..0000000 --- a/services/nuldoc/nuldoc-src/components/Pagination.tsx +++ /dev/null @@ -1,104 +0,0 @@ -type Props = { - currentPage: number; - totalPages: number; - basePath: string; -}; - -export default function Pagination( - { currentPage, totalPages, basePath }: Props, -) { - if (totalPages <= 1) { - return
; - } - - const pages = generatePageNumbers(currentPage, totalPages); - - return ( - - ); -} - -type PageItem = number | "..."; - -/** - * Generates page numbers for pagination display. - * - * - Always show the first page - * - Always show the last page - * - Always show the current page - * - Always show the page before and after the current page - * - If there's only one page gap between displayed pages, fill it - * - If there are two or more pages gap between displayed pages, show ellipsis - */ -function generatePageNumbers( - currentPage: number, - totalPages: number, -): PageItem[] { - const pages = new Set(); - pages.add(1); - pages.add(Math.max(1, currentPage - 1)); - pages.add(currentPage); - pages.add(Math.min(totalPages, currentPage + 1)); - pages.add(totalPages); - - const sorted = Array.from(pages).sort((a, b) => a - b); - - const result: PageItem[] = []; - for (let i = 0; i < sorted.length; i++) { - if (i > 0) { - const gap = sorted[i] - sorted[i - 1]; - if (gap === 2) { - result.push(sorted[i - 1] + 1); - } else if (gap > 2) { - result.push("..."); - } - } - result.push(sorted[i]); - } - - return result; -} - -function pageUrlAt(basePath: string, page: number): string { - return page === 1 ? basePath : `${basePath}${page}/`; -} diff --git a/services/nuldoc/nuldoc-src/components/PostPageEntry.ts b/services/nuldoc/nuldoc-src/components/PostPageEntry.ts new file mode 100644 index 0000000..75ad11c --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/PostPageEntry.ts @@ -0,0 +1,49 @@ +import { + getPostPublishedDate, + getPostUpdatedDate, + postHasAnyUpdates, + PostPage, +} from "../generators/post.ts"; +import { dateToString } from "../revision.ts"; +import { Config } from "../config.ts"; +import { elem, Element } from "../dom.ts"; +import TagList from "./TagList.ts"; + +type Props = { post: PostPage; config: Config }; + +export default function PostPageEntry({ post, config }: Props): Element { + return elem( + "article", + { class: "post-entry" }, + elem( + "a", + { href: post.href }, + elem("header", { class: "entry-header" }, elem("h2", {}, post.title)), + elem( + "section", + { class: "entry-content" }, + elem("p", {}, post.description), + ), + elem( + "footer", + { class: "entry-footer" }, + elem( + "time", + { datetime: dateToString(getPostPublishedDate(post)) }, + dateToString(getPostPublishedDate(post)), + ), + " 投稿", + postHasAnyUpdates(post) ? "、" : null, + postHasAnyUpdates(post) + ? elem( + "time", + { datetime: dateToString(getPostUpdatedDate(post)) }, + dateToString(getPostUpdatedDate(post)), + ) + : null, + postHasAnyUpdates(post) ? " 更新" : null, + post.tags.length !== 0 ? TagList({ tags: post.tags, config }) : null, + ), + ), + ); +} diff --git a/services/nuldoc/nuldoc-src/components/PostPageEntry.tsx b/services/nuldoc/nuldoc-src/components/PostPageEntry.tsx deleted file mode 100644 index 23ca88a..0000000 --- a/services/nuldoc/nuldoc-src/components/PostPageEntry.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { - getPostPublishedDate, - getPostUpdatedDate, - postHasAnyUpdates, - PostPage, -} from "../generators/post.ts"; -import { dateToString } from "../revision.ts"; -import { Config } from "../config.ts"; -import TagList from "./TagList.tsx"; - -type Props = { post: PostPage; config: Config }; - -export default function PostPageEntry({ post, config }: Props) { - return ( - - ); -} diff --git a/services/nuldoc/nuldoc-src/components/SlidePageEntry.ts b/services/nuldoc/nuldoc-src/components/SlidePageEntry.ts new file mode 100644 index 0000000..1dc5f1a --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/SlidePageEntry.ts @@ -0,0 +1,49 @@ +import { + getPostPublishedDate, + getPostUpdatedDate, + postHasAnyUpdates, +} from "../generators/post.ts"; +import { SlidePage } from "../generators/slide.ts"; +import { dateToString } from "../revision.ts"; +import { Config } from "../config.ts"; +import { elem, Element } from "../dom.ts"; +import TagList from "./TagList.ts"; + +type Props = { slide: SlidePage; config: Config }; + +export default function SlidePageEntry({ slide, config }: Props): Element { + return elem( + "article", + { class: "post-entry" }, + elem( + "a", + { href: slide.href }, + elem( + "header", + { class: "entry-header" }, + elem("h2", {}, slide.description), + ), + elem("section", { class: "entry-content" }, elem("p", {}, slide.title)), + elem( + "footer", + { class: "entry-footer" }, + elem( + "time", + { datetime: dateToString(getPostPublishedDate(slide)) }, + dateToString(getPostPublishedDate(slide)), + ), + " 登壇", + postHasAnyUpdates(slide) ? "、" : null, + postHasAnyUpdates(slide) + ? elem( + "time", + { datetime: dateToString(getPostUpdatedDate(slide)) }, + dateToString(getPostUpdatedDate(slide)), + ) + : null, + postHasAnyUpdates(slide) ? " 更新" : null, + slide.tags.length !== 0 ? TagList({ tags: slide.tags, config }) : null, + ), + ), + ); +} diff --git a/services/nuldoc/nuldoc-src/components/SlidePageEntry.tsx b/services/nuldoc/nuldoc-src/components/SlidePageEntry.tsx deleted file mode 100644 index 2401765..0000000 --- a/services/nuldoc/nuldoc-src/components/SlidePageEntry.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { - getPostPublishedDate, - getPostUpdatedDate, - postHasAnyUpdates, -} from "../generators/post.ts"; -import { SlidePage } from "../generators/slide.ts"; -import { dateToString } from "../revision.ts"; -import { Config } from "../config.ts"; -import TagList from "./TagList.tsx"; - -type Props = { slide: SlidePage; config: Config }; - -export default function SlidePageEntry({ slide, config }: Props) { - return ( - - ); -} diff --git a/services/nuldoc/nuldoc-src/components/SlidesGlobalHeader.ts b/services/nuldoc/nuldoc-src/components/SlidesGlobalHeader.ts new file mode 100644 index 0000000..902e12f --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/SlidesGlobalHeader.ts @@ -0,0 +1,33 @@ +import { Config } from "../config.ts"; +import { elem, Element } from "../dom.ts"; + +export default function GlobalHeader({ config }: { config: Config }): Element { + return elem( + "header", + { class: "header" }, + elem( + "div", + { class: "site-logo" }, + elem( + "a", + { href: `https://${config.sites.default.fqdn}/` }, + "nsfisis.dev", + ), + ), + elem( + "nav", + { class: "nav" }, + elem( + "ul", + {}, + elem( + "li", + {}, + elem("a", { href: `https://${config.sites.about.fqdn}/` }, "About"), + ), + elem("li", {}, elem("a", { href: "/slides/" }, "Slides")), + elem("li", {}, elem("a", { href: "/tags/" }, "Tags")), + ), + ), + ); +} diff --git a/services/nuldoc/nuldoc-src/components/SlidesGlobalHeader.tsx b/services/nuldoc/nuldoc-src/components/SlidesGlobalHeader.tsx deleted file mode 100644 index 4d93240..0000000 --- a/services/nuldoc/nuldoc-src/components/SlidesGlobalHeader.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Config } from "../config.ts"; - -export default function GlobalHeader({ config }: { config: Config }) { - return ( -
- - -
- ); -} diff --git a/services/nuldoc/nuldoc-src/components/StaticScript.ts b/services/nuldoc/nuldoc-src/components/StaticScript.ts new file mode 100644 index 0000000..7df40fd --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/StaticScript.ts @@ -0,0 +1,20 @@ +import { join } from "@std/path"; +import { Config } from "../config.ts"; +import { elem, Element } from "../dom.ts"; +import { calculateFileHash } from "./utils.ts"; + +export default async function StaticScript( + { fileName, type, defer, config }: { + fileName: string; + type?: string; + defer?: "true"; + config: Config; + }, +): Promise { + const filePath = join(Deno.cwd(), config.locations.staticDir, fileName); + const hash = await calculateFileHash(filePath); + const attrs: Record = { src: `${fileName}?h=${hash}` }; + if (type) attrs.type = type; + if (defer) attrs.defer = defer; + return elem("script", attrs); +} diff --git a/services/nuldoc/nuldoc-src/components/StaticScript.tsx b/services/nuldoc/nuldoc-src/components/StaticScript.tsx deleted file mode 100644 index 0e3ab19..0000000 --- a/services/nuldoc/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 ( - - ); -} diff --git a/services/nuldoc/nuldoc-src/components/StaticStylesheet.ts b/services/nuldoc/nuldoc-src/components/StaticStylesheet.ts new file mode 100644 index 0000000..43802d2 --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/StaticStylesheet.ts @@ -0,0 +1,12 @@ +import { join } from "@std/path"; +import { Config } from "../config.ts"; +import { elem, Element } from "../dom.ts"; +import { calculateFileHash } from "./utils.ts"; + +export default async function StaticStylesheet( + { fileName, config }: { fileName: string; config: Config }, +): Promise { + const filePath = join(Deno.cwd(), config.locations.staticDir, fileName); + const hash = await calculateFileHash(filePath); + return elem("link", { rel: "stylesheet", href: `${fileName}?h=${hash}` }); +} diff --git a/services/nuldoc/nuldoc-src/components/StaticStylesheet.tsx b/services/nuldoc/nuldoc-src/components/StaticStylesheet.tsx deleted file mode 100644 index 52b695e..0000000 --- a/services/nuldoc/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 ; -} diff --git a/services/nuldoc/nuldoc-src/components/TableOfContents.ts b/services/nuldoc/nuldoc-src/components/TableOfContents.ts new file mode 100644 index 0000000..ac4205a --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/TableOfContents.ts @@ -0,0 +1,34 @@ +import { TocEntry, TocRoot } from "../djot/document.ts"; +import { elem, Element } from "../dom.ts"; + +type Props = { + toc: TocRoot; +}; + +export default function TableOfContents({ toc }: Props): Element { + return elem( + "nav", + { class: "toc" }, + elem("h2", {}, "目次"), + elem( + "ul", + {}, + ...toc.entries.map((entry) => TocEntryComponent({ entry })), + ), + ); +} + +function TocEntryComponent({ entry }: { entry: TocEntry }): Element { + return elem( + "li", + {}, + elem("a", { href: `#${entry.id}` }, entry.text), + entry.children.length > 0 + ? elem( + "ul", + {}, + ...entry.children.map((child) => TocEntryComponent({ entry: child })), + ) + : null, + ); +} diff --git a/services/nuldoc/nuldoc-src/components/TableOfContents.tsx b/services/nuldoc/nuldoc-src/components/TableOfContents.tsx deleted file mode 100644 index 29907d0..0000000 --- a/services/nuldoc/nuldoc-src/components/TableOfContents.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { TocEntry, TocRoot } from "../djot/document.ts"; - -type Props = { - toc: TocRoot; -}; - -export default function TableOfContents({ toc }: Props) { - return ( - - ); -} - -function TocEntryComponent({ entry }: { entry: TocEntry }) { - return ( -
  • - {entry.text} - {entry.children.length > 0 && ( -
      - {entry.children.map((child, index) => ( - - ))} -
    - )} -
  • - ); -} diff --git a/services/nuldoc/nuldoc-src/components/TagList.ts b/services/nuldoc/nuldoc-src/components/TagList.ts new file mode 100644 index 0000000..540abe6 --- /dev/null +++ b/services/nuldoc/nuldoc-src/components/TagList.ts @@ -0,0 +1,17 @@ +import { Config, getTagLabel } from "../config.ts"; +import { elem, Element, text } from "../dom.ts"; + +type Props = { + tags: string[]; + config: Config; +}; + +export default function TagList({ tags, config }: Props): Element { + return elem( + "ul", + { class: "entry-tags" }, + ...tags.map((slug) => + elem("li", { class: "tag" }, text(getTagLabel(config, slug))) + ), + ); +} diff --git a/services/nuldoc/nuldoc-src/components/TagList.tsx b/services/nuldoc/nuldoc-src/components/TagList.tsx deleted file mode 100644 index 86ee70b..0000000 --- a/services/nuldoc/nuldoc-src/components/TagList.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Config, getTagLabel } from "../config.ts"; - -type Props = { - tags: string[]; - config: Config; -}; - -export default function TagList({ tags, config }: Props) { - return ( -
      - {tags.map((slug) => ( -
    • - {getTagLabel(config, slug)} -
    • - ))} -
    - ); -} diff --git a/services/nuldoc/nuldoc-src/dom.ts b/services/nuldoc/nuldoc-src/dom.ts index abe7ff8..be503a3 100644 --- a/services/nuldoc/nuldoc-src/dom.ts +++ b/services/nuldoc/nuldoc-src/dom.ts @@ -17,6 +17,25 @@ export type Element = { export type Node = Element | Text | RawHTML; +export type NodeLike = Node | string | null | undefined | false | NodeLike[]; + +function flattenChildren(children: NodeLike[]): Node[] { + const result: Node[] = []; + for (const child of children) { + if (child === null || child === undefined || child === false) { + continue; + } + if (typeof child === "string") { + result.push(text(child)); + } else if (Array.isArray(child)) { + result.push(...flattenChildren(child)); + } else { + result.push(child); + } + } + return result; +} + export function text(content: string): Text { return { kind: "text", @@ -34,13 +53,13 @@ export function rawHTML(html: string): RawHTML { export function elem( name: string, attributes?: Record, - ...children: Node[] + ...children: NodeLike[] ): Element { return { kind: "element", name, attributes: attributes || {}, - children, + children: flattenChildren(children), }; } diff --git a/services/nuldoc/nuldoc-src/generators/about.ts b/services/nuldoc/nuldoc-src/generators/about.ts index 711c167..628c370 100644 --- a/services/nuldoc/nuldoc-src/generators/about.ts +++ b/services/nuldoc/nuldoc-src/generators/about.ts @@ -1,6 +1,5 @@ -import AboutPage from "../pages/AboutPage.tsx"; +import AboutPage from "../pages/AboutPage.ts"; import { Config } from "../config.ts"; -import { renderToDOM } from "../jsx/render.ts"; import { Page } from "../page.ts"; import { SlidePage } from "./slide.ts"; @@ -10,9 +9,7 @@ export async function generateAboutPage( slides: SlidePage[], config: Config, ): Promise { - const html = await renderToDOM( - AboutPage(slides, config), - ); + const html = await AboutPage(slides, config); return { root: html, diff --git a/services/nuldoc/nuldoc-src/generators/atom.ts b/services/nuldoc/nuldoc-src/generators/atom.ts index dc62da9..f501d83 100644 --- a/services/nuldoc/nuldoc-src/generators/atom.ts +++ b/services/nuldoc/nuldoc-src/generators/atom.ts @@ -3,8 +3,7 @@ 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"; +import AtomPage from "../pages/AtomPage.ts"; export type Feed = { author: string; @@ -28,14 +27,14 @@ export type Entry = { const BASE_NAME = "atom.xml"; -export async function generateFeedPageFromEntries( +export function generateFeedPageFromEntries( alternateLink: string, feedSlug: string, feedTitle: string, entries: Array, site: "default" | "blog" | "slides", config: Config, -): Promise { +): Page { const entries_: Entry[] = []; for (const entry of entries) { entries_.push({ @@ -76,7 +75,7 @@ export async function generateFeedPageFromEntries( }; return { - root: await renderToDOM(AtomPage({ feed: feed })), + root: AtomPage({ feed: feed }), renderer: "xml", site, destFilePath: feedPath, diff --git a/services/nuldoc/nuldoc-src/generators/home.ts b/services/nuldoc/nuldoc-src/generators/home.ts index ac91637..1839f5d 100644 --- a/services/nuldoc/nuldoc-src/generators/home.ts +++ b/services/nuldoc/nuldoc-src/generators/home.ts @@ -1,14 +1,11 @@ -import HomePage from "../pages/HomePage.tsx"; -import { renderToDOM } from "../jsx/render.ts"; +import HomePage from "../pages/HomePage.ts"; import { Config } from "../config.ts"; import { Page } from "../page.ts"; export type HomePage = Page; export async function generateHomePage(config: Config): Promise { - const html = await renderToDOM( - HomePage(config), - ); + const html = await HomePage(config); return { root: html, diff --git a/services/nuldoc/nuldoc-src/generators/not_found.ts b/services/nuldoc/nuldoc-src/generators/not_found.ts index 56adc8e..8a5593c 100644 --- a/services/nuldoc/nuldoc-src/generators/not_found.ts +++ b/services/nuldoc/nuldoc-src/generators/not_found.ts @@ -1,5 +1,4 @@ -import NotFoundPage from "../pages/NotFoundPage.tsx"; -import { renderToDOM } from "../jsx/render.ts"; +import NotFoundPage from "../pages/NotFoundPage.ts"; import { Config } from "../config.ts"; import { Page } from "../page.ts"; @@ -9,9 +8,7 @@ export async function generateNotFoundPage( site: "default" | "about" | "blog" | "slides", config: Config, ): Promise { - const html = await renderToDOM( - NotFoundPage(site, config), - ); + const html = await NotFoundPage(site, config); return { root: html, diff --git a/services/nuldoc/nuldoc-src/generators/post.ts b/services/nuldoc/nuldoc-src/generators/post.ts index 4e08f88..11a3ce8 100644 --- a/services/nuldoc/nuldoc-src/generators/post.ts +++ b/services/nuldoc/nuldoc-src/generators/post.ts @@ -1,6 +1,5 @@ import { join } from "@std/path"; -import { renderToDOM } from "../jsx/render.ts"; -import PostPage from "../pages/PostPage.tsx"; +import PostPage from "../pages/PostPage.ts"; import { Config } from "../config.ts"; import { Document } from "../djot/document.ts"; import { Page } from "../page.ts"; @@ -37,9 +36,7 @@ export async function generatePostPage( doc: Document, config: Config, ): Promise { - const html = await renderToDOM( - PostPage(doc, config), - ); + const html = await PostPage(doc, config); const cwd = Deno.cwd(); const contentDir = join(cwd, config.locations.contentDir); diff --git a/services/nuldoc/nuldoc-src/generators/post_list.ts b/services/nuldoc/nuldoc-src/generators/post_list.ts index 6a21dd8..cb3d5c8 100644 --- a/services/nuldoc/nuldoc-src/generators/post_list.ts +++ b/services/nuldoc/nuldoc-src/generators/post_list.ts @@ -1,5 +1,4 @@ -import { renderToDOM } from "../jsx/render.ts"; -import PostListPage from "../pages/PostListPage.tsx"; +import PostListPage from "../pages/PostListPage.ts"; import { Config } from "../config.ts"; import { Page } from "../page.ts"; import { PostPage } from "./post.ts"; @@ -39,14 +38,7 @@ async function generatePostListPage( currentPage: number, totalPages: number, ): Promise { - const html = await renderToDOM( - PostListPage( - posts, - config, - currentPage, - totalPages, - ), - ); + const html = await PostListPage(posts, config, currentPage, totalPages); const destFilePath = currentPage === 1 ? "/posts/index.html" diff --git a/services/nuldoc/nuldoc-src/generators/slide.ts b/services/nuldoc/nuldoc-src/generators/slide.ts index feab583..2c04b40 100644 --- a/services/nuldoc/nuldoc-src/generators/slide.ts +++ b/services/nuldoc/nuldoc-src/generators/slide.ts @@ -1,6 +1,5 @@ import { join } from "@std/path"; -import { renderToDOM } from "../jsx/render.ts"; -import SlidePage from "../pages/SlidePage.tsx"; +import SlidePage from "../pages/SlidePage.ts"; import { Config } from "../config.ts"; import { Page } from "../page.ts"; import { Date, Revision } from "../revision.ts"; @@ -24,9 +23,7 @@ export async function generateSlidePage( slide: Slide, config: Config, ): Promise { - const html = await renderToDOM( - SlidePage(slide, config), - ); + const html = await SlidePage(slide, config); const cwd = Deno.cwd(); const contentDir = join(cwd, config.locations.contentDir); diff --git a/services/nuldoc/nuldoc-src/generators/slide_list.ts b/services/nuldoc/nuldoc-src/generators/slide_list.ts index 9f766ed..b65c9db 100644 --- a/services/nuldoc/nuldoc-src/generators/slide_list.ts +++ b/services/nuldoc/nuldoc-src/generators/slide_list.ts @@ -1,5 +1,4 @@ -import { renderToDOM } from "../jsx/render.ts"; -import SlideListPage from "../pages/SlideListPage.tsx"; +import SlideListPage from "../pages/SlideListPage.ts"; import { Config } from "../config.ts"; import { Page } from "../page.ts"; import { SlidePage } from "./slide.ts"; @@ -10,9 +9,7 @@ export async function generateSlideListPage( slides: SlidePage[], config: Config, ): Promise { - const html = await renderToDOM( - SlideListPage(slides, config), - ); + const html = await SlideListPage(slides, config); return { root: html, diff --git a/services/nuldoc/nuldoc-src/generators/tag.ts b/services/nuldoc/nuldoc-src/generators/tag.ts index 11335c6..efe2da5 100644 --- a/services/nuldoc/nuldoc-src/generators/tag.ts +++ b/services/nuldoc/nuldoc-src/generators/tag.ts @@ -1,5 +1,4 @@ -import { renderToDOM } from "../jsx/render.ts"; -import TagPage from "../pages/TagPage.tsx"; +import TagPage from "../pages/TagPage.ts"; import { Config, getTagLabel } from "../config.ts"; import { Page } from "../page.ts"; import { TaggedPage } from "./tagged_page.ts"; @@ -17,9 +16,7 @@ export async function generateTagPage( site: "blog" | "slides", config: Config, ): Promise { - const html = await renderToDOM( - TagPage(tagSlug, pages, site, config), - ); + const html = await TagPage(tagSlug, pages, site, config); return { root: html, diff --git a/services/nuldoc/nuldoc-src/generators/tag_list.ts b/services/nuldoc/nuldoc-src/generators/tag_list.ts index cbc161e..96faa66 100644 --- a/services/nuldoc/nuldoc-src/generators/tag_list.ts +++ b/services/nuldoc/nuldoc-src/generators/tag_list.ts @@ -1,5 +1,4 @@ -import { renderToDOM } from "../jsx/render.ts"; -import TagListPage from "../pages/TagListPage.tsx"; +import TagListPage from "../pages/TagListPage.ts"; import { Config } from "../config.ts"; import { Page } from "../page.ts"; import { TagPage } from "./tag.ts"; @@ -11,9 +10,7 @@ export async function generateTagListPage( site: "blog" | "slides", config: Config, ): Promise { - const html = await renderToDOM( - TagListPage(tags, site, config), - ); + const html = await TagListPage(tags, site, config); return { root: html, diff --git a/services/nuldoc/nuldoc-src/jsx/jsx-runtime.ts b/services/nuldoc/nuldoc-src/jsx/jsx-runtime.ts deleted file mode 100644 index 9571e87..0000000 --- a/services/nuldoc/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; -export type FunctionComponentResult = JSXElement | Promise; -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/services/nuldoc/nuldoc-src/jsx/render.ts b/services/nuldoc/nuldoc-src/jsx/render.ts deleted file mode 100644 index a72d9ad..0000000 --- a/services/nuldoc/nuldoc-src/jsx/render.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Element, Node } from "../dom.ts"; -import { elem, text } from "../dom.ts"; -import type { - JSXNode, - JSXNullableSimpleNode, - JSXSimpleNode, - RenderableJSXNode, -} from "myjsx/jsx-runtime"; - -function transformNode(node: JSXNode): Promise { - 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 text(c); - } else if ("kind" in c) { - return c; - } else { - return renderToDOM(c); - } - }), - ); -} - -export async function renderToDOM( - element: RenderableJSXNode, -): Promise { - const { tag, props } = element; - if (typeof tag === "string") { - const { children, ...attrs } = props; - const attrsMap = attrs as Record; - return elem(tag, attrsMap, ...(await transformNode(children))); - } else { - return renderToDOM(await tag(props)); - } -} diff --git a/services/nuldoc/nuldoc-src/jsx/types.d.ts b/services/nuldoc/nuldoc-src/jsx/types.d.ts deleted file mode 100644 index 973b852..0000000 --- a/services/nuldoc/nuldoc-src/jsx/types.d.ts +++ /dev/null @@ -1,84 +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; - span: 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/services/nuldoc/nuldoc-src/pages/AboutPage.ts b/services/nuldoc/nuldoc-src/pages/AboutPage.ts new file mode 100644 index 0000000..43e239e --- /dev/null +++ b/services/nuldoc/nuldoc-src/pages/AboutPage.ts @@ -0,0 +1,160 @@ +import GlobalFooter from "../components/GlobalFooter.ts"; +import GlobalHeader from "../components/AboutGlobalHeader.ts"; +import PageLayout from "../components/PageLayout.ts"; +import StaticScript from "../components/StaticScript.ts"; +import { Config } from "../config.ts"; +import { dateToString } from "../revision.ts"; +import { getPostPublishedDate } from "../generators/post.ts"; +import { SlidePage } from "../generators/slide.ts"; +import { elem, Element } from "../dom.ts"; + +export default async function AboutPage( + slides: SlidePage[], + config: Config, +): Promise { + return await PageLayout({ + metaCopyrightYear: config.site.copyrightYear, + metaDescription: "このサイトの著者について", + metaTitle: `About|${config.sites.about.siteName}`, + site: "about", + config, + children: elem( + "body", + { class: "single" }, + GlobalHeader({ config }), + elem( + "main", + { class: "main" }, + elem( + "article", + { class: "post-single" }, + elem( + "header", + { class: "post-header" }, + elem("h1", { class: "post-title" }, "nsfisis"), + elem( + "div", + { class: "my-icon" }, + elem( + "div", + { id: "myIcon" }, + elem("img", { src: "/favicon.svg" }), + ), + await StaticScript({ + fileName: "/my-icon.js", + defer: "true", + config, + }), + ), + ), + elem( + "div", + { class: "post-content" }, + elem( + "section", + {}, + elem("h2", {}, "読み方"), + elem( + "p", + {}, + "読み方は決めていません。音にする必要があるときは本名である「いまむら」をお使いください。", + ), + ), + elem( + "section", + {}, + elem("h2", {}, "アカウント"), + elem( + "ul", + {}, + elem( + "li", + {}, + elem( + "a", + { + href: "https://twitter.com/nsfisis", + target: "_blank", + rel: "noreferrer", + }, + "Twitter (現 𝕏): @nsfisis", + ), + ), + elem( + "li", + {}, + elem( + "a", + { + href: "https://github.com/nsfisis", + target: "_blank", + rel: "noreferrer", + }, + "GitHub: @nsfisis", + ), + ), + ), + ), + elem( + "section", + {}, + elem("h2", {}, "仕事"), + elem( + "ul", + {}, + elem( + "li", + {}, + "2021-01~現在: ", + elem( + "a", + { + href: "https://www.dgcircus.com/", + target: "_blank", + rel: "noreferrer", + }, + "デジタルサーカス株式会社", + ), + ), + ), + ), + elem( + "section", + {}, + elem("h2", {}, "登壇"), + elem( + "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) => + elem( + "li", + {}, + elem( + "a", + { + href: + `https://${config.sites.slides.fqdn}${slide.href}`, + }, + `${ + dateToString(getPostPublishedDate(slide)) + }: ${slide.event} (${slide.talkType})`, + ), + ) + ), + ), + ), + ), + ), + ), + GlobalFooter({ config }), + ), + }); +} diff --git a/services/nuldoc/nuldoc-src/pages/AboutPage.tsx b/services/nuldoc/nuldoc-src/pages/AboutPage.tsx deleted file mode 100644 index d0397c1..0000000 --- a/services/nuldoc/nuldoc-src/pages/AboutPage.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import GlobalFooter from "../components/GlobalFooter.tsx"; -import GlobalHeader from "../components/AboutGlobalHeader.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 ( - - - -
    -
    -
    -

    nsfisis

    -
    -
    - -
    - -
    -
    -
    -
    -

    読み方

    -

    - 読み方は決めていません。音にする必要があるときは本名である「いまむら」をお使いください。 -

    -
    -
    -

    アカウント

    - -
    -
    -

    仕事

    - -
    -
    -

    登壇

    - -
    -
    -
    -
    - - -
    - ); -} diff --git a/services/nuldoc/nuldoc-src/pages/AtomPage.ts b/services/nuldoc/nuldoc-src/pages/AtomPage.ts new file mode 100644 index 0000000..f270972 --- /dev/null +++ b/services/nuldoc/nuldoc-src/pages/AtomPage.ts @@ -0,0 +1,27 @@ +import { Feed } from "../generators/atom.ts"; +import { elem, Element } from "../dom.ts"; + +export default function AtomPage({ feed }: { feed: Feed }): Element { + return elem( + "feed", + { xmlns: "http://www.w3.org/2005/Atom" }, + elem("id", {}, feed.id), + elem("title", {}, feed.title), + elem("link", { rel: "alternate", href: feed.linkToAlternate }), + elem("link", { rel: "self", href: feed.linkToSelf }), + elem("author", {}, elem("name", {}, feed.author)), + elem("updated", {}, feed.updated), + ...feed.entries.map((entry) => + elem( + "entry", + {}, + elem("id", {}, entry.id), + elem("link", { rel: "alternate", href: entry.linkToAlternate }), + elem("title", {}, entry.title), + elem("summary", {}, entry.summary), + elem("published", {}, entry.published), + elem("updated", {}, entry.updated), + ) + ), + ); +} diff --git a/services/nuldoc/nuldoc-src/pages/AtomPage.tsx b/services/nuldoc/nuldoc-src/pages/AtomPage.tsx deleted file mode 100644 index 21c3bfa..0000000 --- a/services/nuldoc/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.id} - {feed.title} - - - - {feed.author} - - {feed.updated} - {feed.entries.map((entry) => ( - - {entry.id} - - {entry.title} - {entry.summary} - {entry.published} - {entry.updated} - - ))} - - ); -} diff --git a/services/nuldoc/nuldoc-src/pages/HomePage.ts b/services/nuldoc/nuldoc-src/pages/HomePage.ts new file mode 100644 index 0000000..503a382 --- /dev/null +++ b/services/nuldoc/nuldoc-src/pages/HomePage.ts @@ -0,0 +1,78 @@ +import GlobalFooter from "../components/GlobalFooter.ts"; +import GlobalHeader from "../components/DefaultGlobalHeader.ts"; +import PageLayout from "../components/PageLayout.ts"; +import { Config } from "../config.ts"; +import { elem, Element } from "../dom.ts"; + +export default async function HomePage(config: Config): Promise { + return await PageLayout({ + metaCopyrightYear: config.site.copyrightYear, + metaDescription: "nsfisis のサイト", + metaTitle: config.sites.default.siteName, + metaAtomFeedHref: `https://${config.sites.default.fqdn}/atom.xml`, + site: "default", + config, + children: elem( + "body", + { class: "single" }, + GlobalHeader({ config }), + elem( + "main", + { class: "main" }, + elem( + "article", + { class: "post-single" }, + elem( + "article", + { class: "post-entry" }, + elem( + "a", + { href: `https://${config.sites.about.fqdn}/` }, + elem( + "header", + { class: "entry-header" }, + elem("h2", {}, "About"), + ), + ), + ), + elem( + "article", + { class: "post-entry" }, + elem( + "a", + { href: `https://${config.sites.blog.fqdn}/posts/` }, + elem("header", { class: "entry-header" }, elem("h2", {}, "Blog")), + ), + ), + elem( + "article", + { class: "post-entry" }, + elem( + "a", + { href: `https://${config.sites.slides.fqdn}/slides/` }, + elem( + "header", + { class: "entry-header" }, + elem("h2", {}, "Slides"), + ), + ), + ), + elem( + "article", + { class: "post-entry" }, + elem( + "a", + { href: `https://repos.${config.sites.default.fqdn}/` }, + elem( + "header", + { class: "entry-header" }, + elem("h2", {}, "Repositories"), + ), + ), + ), + ), + ), + GlobalFooter({ config }), + ), + }); +} diff --git a/services/nuldoc/nuldoc-src/pages/HomePage.tsx b/services/nuldoc/nuldoc-src/pages/HomePage.tsx deleted file mode 100644 index a2cbdd1..0000000 --- a/services/nuldoc/nuldoc-src/pages/HomePage.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import GlobalFooter from "../components/GlobalFooter.tsx"; -import GlobalHeader from "../components/DefaultGlobalHeader.tsx"; -import PageLayout from "../components/PageLayout.tsx"; -import { Config } from "../config.ts"; - -export default function HomePage(config: Config) { - return ( - - - -
    - -
    - - -
    - ); -} diff --git a/services/nuldoc/nuldoc-src/pages/NotFoundPage.ts b/services/nuldoc/nuldoc-src/pages/NotFoundPage.ts new file mode 100644 index 0000000..34854c4 --- /dev/null +++ b/services/nuldoc/nuldoc-src/pages/NotFoundPage.ts @@ -0,0 +1,40 @@ +import GlobalFooter from "../components/GlobalFooter.ts"; +import AboutGlobalHeader from "../components/AboutGlobalHeader.ts"; +import BlogGlobalHeader from "../components/BlogGlobalHeader.ts"; +import SlidesGlobalHeader from "../components/SlidesGlobalHeader.ts"; +import DefaultGlobalHeader from "../components/DefaultGlobalHeader.ts"; +import PageLayout from "../components/PageLayout.ts"; +import { Config } from "../config.ts"; +import { elem, Element } from "../dom.ts"; + +export default async function NotFoundPage( + site: "default" | "about" | "blog" | "slides", + config: Config, +): Promise { + const GlobalHeader = site === "about" + ? AboutGlobalHeader + : site === "blog" + ? BlogGlobalHeader + : site === "slides" + ? SlidesGlobalHeader + : DefaultGlobalHeader; + + return await PageLayout({ + metaCopyrightYear: config.site.copyrightYear, + metaDescription: "リクエストされたページが見つかりません", + metaTitle: `Page Not Found|${config.sites[site].siteName}`, + site, + config, + children: elem( + "body", + { class: "single" }, + GlobalHeader({ config }), + elem( + "main", + { class: "main" }, + elem("article", {}, elem("div", { class: "not-found" }, "404")), + ), + GlobalFooter({ config }), + ), + }); +} diff --git a/services/nuldoc/nuldoc-src/pages/NotFoundPage.tsx b/services/nuldoc/nuldoc-src/pages/NotFoundPage.tsx deleted file mode 100644 index 11ce518..0000000 --- a/services/nuldoc/nuldoc-src/pages/NotFoundPage.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import GlobalFooter from "../components/GlobalFooter.tsx"; -import AboutGlobalHeader from "../components/AboutGlobalHeader.tsx"; -import BlogGlobalHeader from "../components/BlogGlobalHeader.tsx"; -import SlidesGlobalHeader from "../components/SlidesGlobalHeader.tsx"; -import DefaultGlobalHeader from "../components/DefaultGlobalHeader.tsx"; -import PageLayout from "../components/PageLayout.tsx"; -import { Config } from "../config.ts"; - -export default function NotFoundPage( - site: "default" | "about" | "blog" | "slides", - config: Config, -) { - return ( - - - {site === "about" - ? - : site === "blog" - ? - : site === "slides" - ? - : } -
    -
    -
    404
    -
    -
    - - -
    - ); -} diff --git a/services/nuldoc/nuldoc-src/pages/PostListPage.ts b/services/nuldoc/nuldoc-src/pages/PostListPage.ts new file mode 100644 index 0000000..53b8fa4 --- /dev/null +++ b/services/nuldoc/nuldoc-src/pages/PostListPage.ts @@ -0,0 +1,49 @@ +import GlobalFooter from "../components/GlobalFooter.ts"; +import GlobalHeader from "../components/BlogGlobalHeader.ts"; +import PageLayout from "../components/PageLayout.ts"; +import Pagination from "../components/Pagination.ts"; +import PostPageEntry from "../components/PostPageEntry.ts"; +import { Config } from "../config.ts"; +import { PostPage } from "../generators/post.ts"; +import { elem, Element } from "../dom.ts"; + +export default async function PostListPage( + posts: PostPage[], + config: Config, + currentPage: number, + totalPages: number, +): Promise { + const pageTitle = "投稿一覧"; + + const pageInfoSuffix = ` (${currentPage}ページ目)`; + const metaTitle = + `${pageTitle}${pageInfoSuffix}|${config.sites.blog.siteName}`; + const metaDescription = `投稿した記事の一覧${pageInfoSuffix}`; + + return await PageLayout({ + metaCopyrightYear: config.site.copyrightYear, + metaDescription, + metaTitle, + metaAtomFeedHref: `https://${config.sites.blog.fqdn}/posts/atom.xml`, + site: "blog", + config, + children: elem( + "body", + { class: "list" }, + GlobalHeader({ config }), + elem( + "main", + { class: "main" }, + elem( + "header", + { class: "page-header" }, + elem("h1", {}, pageTitle + pageInfoSuffix), + ), + Pagination({ currentPage, totalPages, basePath: "/posts/" }), + ...posts.map((post) => PostPageEntry({ post, config })), + Pagination({ currentPage, totalPages, basePath: "/posts/" }), + ), + GlobalFooter({ config }), + ), + }); +} diff --git a/services/nuldoc/nuldoc-src/pages/PostListPage.tsx b/services/nuldoc/nuldoc-src/pages/PostListPage.tsx deleted file mode 100644 index 5ed9696..0000000 --- a/services/nuldoc/nuldoc-src/pages/PostListPage.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import GlobalFooter from "../components/GlobalFooter.tsx"; -import GlobalHeader from "../components/BlogGlobalHeader.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.sites.blog.siteName}`; - const metaDescription = `投稿した記事の一覧${pageInfoSuffix}`; - - return ( - - - -
    -
    -

    {pageTitle}{pageInfoSuffix}

    -
    - - - - {posts.map((post) => ( - - ))} - - -
    - - -
    - ); -} diff --git a/services/nuldoc/nuldoc-src/pages/PostPage.ts b/services/nuldoc/nuldoc-src/pages/PostPage.ts new file mode 100644 index 0000000..84f58c3 --- /dev/null +++ b/services/nuldoc/nuldoc-src/pages/PostPage.ts @@ -0,0 +1,90 @@ +import GlobalFooter from "../components/GlobalFooter.ts"; +import GlobalHeader from "../components/BlogGlobalHeader.ts"; +import PageLayout from "../components/PageLayout.ts"; +import TableOfContents from "../components/TableOfContents.ts"; +import { Config, getTagLabel } from "../config.ts"; +import { elem, Element } from "../dom.ts"; +import { Document } from "../djot/document.ts"; +import { dateToString } from "../revision.ts"; +import { getPostPublishedDate } from "../generators/post.ts"; + +export default async function PostPage( + doc: Document, + config: Config, +): Promise { + return await PageLayout({ + metaCopyrightYear: getPostPublishedDate(doc).year, + metaDescription: doc.description, + metaKeywords: doc.tags.map((slug) => getTagLabel(config, slug)), + metaTitle: `${doc.title}|${config.sites.blog.siteName}`, + requiresSyntaxHighlight: true, + site: "blog", + config, + children: elem( + "body", + { class: "single" }, + GlobalHeader({ config }), + elem( + "main", + { class: "main" }, + elem( + "article", + { class: "post-single" }, + elem( + "header", + { class: "post-header" }, + elem("h1", { class: "post-title" }, doc.title), + doc.tags.length !== 0 + ? elem( + "ul", + { class: "post-tags" }, + ...doc.tags.map((slug) => + elem( + "li", + { class: "tag" }, + elem( + "a", + { href: `/tags/${slug}/` }, + getTagLabel(config, slug), + ), + ) + ), + ) + : null, + ), + doc.toc && doc.toc.entries.length > 0 + ? TableOfContents({ toc: doc.toc }) + : null, + elem( + "div", + { class: "post-content" }, + elem( + "section", + { id: "changelog" }, + elem("h2", {}, elem("a", { href: "#changelog" }, "更新履歴")), + elem( + "ol", + {}, + ...doc.revisions.map((rev) => + elem( + "li", + { class: "revision" }, + elem( + "time", + { datetime: dateToString(rev.date) }, + dateToString(rev.date), + ), + `: ${rev.remark}`, + ) + ), + ), + ), + // TODO: refactor + ...(doc.root.children[0] as Element).children, + ), + ), + ), + GlobalFooter({ config }), + ), + }); +} diff --git a/services/nuldoc/nuldoc-src/pages/PostPage.tsx b/services/nuldoc/nuldoc-src/pages/PostPage.tsx deleted file mode 100644 index beebdb3..0000000 --- a/services/nuldoc/nuldoc-src/pages/PostPage.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import GlobalFooter from "../components/GlobalFooter.tsx"; -import GlobalHeader from "../components/BlogGlobalHeader.tsx"; -import PageLayout from "../components/PageLayout.tsx"; -import TableOfContents from "../components/TableOfContents.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 ( - getTagLabel(config, slug))} - metaTitle={`${doc.title}|${config.sites.blog.siteName}`} - requiresSyntaxHighlight - site="blog" - config={config} - > - - -
    -
    -
    -

    {doc.title}

    - {doc.tags.length !== 0 && ( - - )} -
    - {doc.toc && doc.toc.entries.length > 0 && ( - - )} -
    -
    -

    - 更新履歴 -

    -
      - {doc.revisions.map((rev) => ( -
    1. - - {`: ${rev.remark}`} -
    2. - ))} -
    -
    - { - // TODO: refactor - (doc.root.children[0] as Element).children - } -
    -
    -
    - - -
    - ); -} diff --git a/services/nuldoc/nuldoc-src/pages/SlideListPage.ts b/services/nuldoc/nuldoc-src/pages/SlideListPage.ts new file mode 100644 index 0000000..9a1c2b2 --- /dev/null +++ b/services/nuldoc/nuldoc-src/pages/SlideListPage.ts @@ -0,0 +1,45 @@ +import GlobalFooter from "../components/GlobalFooter.ts"; +import GlobalHeader from "../components/SlidesGlobalHeader.ts"; +import PageLayout from "../components/PageLayout.ts"; +import SlidePageEntry from "../components/SlidePageEntry.ts"; +import { Config } from "../config.ts"; +import { dateToString } from "../revision.ts"; +import { getPostPublishedDate } from "../generators/post.ts"; +import { SlidePage } from "../generators/slide.ts"; +import { elem, Element } from "../dom.ts"; + +export default async function SlideListPage( + slides: SlidePage[], + config: Config, +): Promise { + const pageTitle = "スライド一覧"; + + return await PageLayout({ + metaCopyrightYear: config.site.copyrightYear, + metaDescription: "登壇したイベントで使用したスライドの一覧", + metaTitle: `${pageTitle}|${config.sites.slides.siteName}`, + metaAtomFeedHref: `https://${config.sites.slides.fqdn}/slides/atom.xml`, + site: "slides", + config, + children: elem( + "body", + { class: "list" }, + GlobalHeader({ config }), + elem( + "main", + { class: "main" }, + elem("header", { class: "page-header" }, elem("h1", {}, pageTitle)), + ...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, config })), + ), + GlobalFooter({ config }), + ), + }); +} diff --git a/services/nuldoc/nuldoc-src/pages/SlideListPage.tsx b/services/nuldoc/nuldoc-src/pages/SlideListPage.tsx deleted file mode 100644 index dc216eb..0000000 --- a/services/nuldoc/nuldoc-src/pages/SlideListPage.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import GlobalFooter from "../components/GlobalFooter.tsx"; -import GlobalHeader from "../components/SlidesGlobalHeader.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 ( - - - -
    -
    -

    {pageTitle}

    -
    - {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) => ( - - ))} -
    - - -
    - ); -} diff --git a/services/nuldoc/nuldoc-src/pages/SlidePage.ts b/services/nuldoc/nuldoc-src/pages/SlidePage.ts new file mode 100644 index 0000000..8699134 --- /dev/null +++ b/services/nuldoc/nuldoc-src/pages/SlidePage.ts @@ -0,0 +1,98 @@ +import GlobalFooter from "../components/GlobalFooter.ts"; +import GlobalHeader from "../components/SlidesGlobalHeader.ts"; +import PageLayout from "../components/PageLayout.ts"; +import StaticScript from "../components/StaticScript.ts"; +import { Config, getTagLabel } from "../config.ts"; +import { dateToString } from "../revision.ts"; +import { Slide } from "../slide/slide.ts"; +import { getPostPublishedDate } from "../generators/post.ts"; +import { elem, Element } from "../dom.ts"; + +export default async function SlidePage( + slide: Slide, + config: Config, +): Promise { + return await PageLayout({ + metaCopyrightYear: getPostPublishedDate(slide).year, + metaDescription: slide.title, + metaKeywords: slide.tags.map((slug) => getTagLabel(config, slug)), + metaTitle: + `${slide.event} (${slide.talkType})|${config.sites.slides.siteName}`, + requiresSyntaxHighlight: true, + site: "slides", + config, + children: elem( + "body", + { class: "single" }, + GlobalHeader({ config }), + elem( + "main", + { class: "main" }, + elem( + "article", + { class: "post-single" }, + elem( + "header", + { class: "post-header" }, + elem("h1", { class: "post-title" }, slide.title), + slide.tags.length !== 0 + ? elem( + "ul", + { class: "post-tags" }, + ...slide.tags.map((slug) => + elem( + "li", + { class: "tag" }, + elem( + "a", + { href: `/tags/${slug}/` }, + getTagLabel(config, slug), + ), + ) + ), + ) + : null, + ), + elem( + "div", + { class: "post-content" }, + elem( + "section", + { id: "changelog" }, + elem("h2", {}, elem("a", { href: "#changelog" }, "更新履歴")), + elem( + "ol", + {}, + ...slide.revisions.map((rev) => + elem( + "li", + { class: "revision" }, + elem( + "time", + { datetime: dateToString(rev.date) }, + dateToString(rev.date), + ), + `: ${rev.remark}`, + ) + ), + ), + ), + elem("canvas", { id: "slide", "data-slide-link": slide.slideLink }), + elem( + "div", + {}, + elem("button", { id: "prev", type: "button" }, "Prev"), + elem("button", { id: "next", type: "button" }, "Next"), + ), + await StaticScript({ + fileName: "/slide.js", + type: "module", + config, + }), + ), + ), + ), + GlobalFooter({ config }), + ), + }); +} diff --git a/services/nuldoc/nuldoc-src/pages/SlidePage.tsx b/services/nuldoc/nuldoc-src/pages/SlidePage.tsx deleted file mode 100644 index 53944df..0000000 --- a/services/nuldoc/nuldoc-src/pages/SlidePage.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import GlobalFooter from "../components/GlobalFooter.tsx"; -import GlobalHeader from "../components/SlidesGlobalHeader.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 ( - getTagLabel(config, slug))} - metaTitle={`${slide.event} (${slide.talkType})|${config.sites.slides.siteName}`} - requiresSyntaxHighlight - site="slides" - config={config} - > - - -
    -
    -
    -

    {slide.title}

    - {slide.tags.length !== 0 && ( - - )} -
    -
    -
    -

    - 更新履歴 -

    -
      - {slide.revisions.map((rev) => ( -
    1. - - {`: ${rev.remark}`} -
    2. - ))} -
    -
    - -
    - - -
    - -
    -
    -
    - - -
    - ); -} diff --git a/services/nuldoc/nuldoc-src/pages/TagListPage.ts b/services/nuldoc/nuldoc-src/pages/TagListPage.ts new file mode 100644 index 0000000..6872a2f --- /dev/null +++ b/services/nuldoc/nuldoc-src/pages/TagListPage.ts @@ -0,0 +1,70 @@ +import GlobalFooter from "../components/GlobalFooter.ts"; +import BlogGlobalHeader from "../components/BlogGlobalHeader.ts"; +import SlidesGlobalHeader from "../components/SlidesGlobalHeader.ts"; +import PageLayout from "../components/PageLayout.ts"; +import { Config } from "../config.ts"; +import { TagPage } from "../generators/tag.ts"; +import { elem, Element } from "../dom.ts"; + +export default async function TagListPage( + tags: TagPage[], + site: "blog" | "slides", + config: Config, +): Promise { + const pageTitle = "タグ一覧"; + + const GlobalHeader = site === "blog" ? BlogGlobalHeader : SlidesGlobalHeader; + + return await PageLayout({ + metaCopyrightYear: config.site.copyrightYear, + metaDescription: "タグの一覧", + metaTitle: `${pageTitle}|${config.sites[site].siteName}`, + site, + config, + children: elem( + "body", + { class: "list" }, + GlobalHeader({ config }), + elem( + "main", + { class: "main" }, + elem("header", { class: "page-header" }, elem("h1", {}, pageTitle)), + ...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) => { + const posts = tag.numOfPosts === 0 + ? "" + : `${tag.numOfPosts}件の記事`; + const slides = tag.numOfSlides === 0 + ? "" + : `${tag.numOfSlides}件のスライド`; + const footerText = `${posts}${ + posts && slides ? "、" : "" + }${slides}`; + + return elem( + "article", + { class: "post-entry" }, + elem( + "a", + { href: tag.href }, + elem( + "header", + { class: "entry-header" }, + elem("h2", {}, tag.tagLabel), + ), + elem("footer", { class: "entry-footer" }, footerText), + ), + ); + }), + ), + GlobalFooter({ config }), + ), + }); +} diff --git a/services/nuldoc/nuldoc-src/pages/TagListPage.tsx b/services/nuldoc/nuldoc-src/pages/TagListPage.tsx deleted file mode 100644 index 3ee58f3..0000000 --- a/services/nuldoc/nuldoc-src/pages/TagListPage.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import GlobalFooter from "../components/GlobalFooter.tsx"; -import BlogGlobalHeader from "../components/BlogGlobalHeader.tsx"; -import SlidesGlobalHeader from "../components/SlidesGlobalHeader.tsx"; -import PageLayout from "../components/PageLayout.tsx"; -import { Config } from "../config.ts"; -import { TagPage } from "../generators/tag.ts"; - -export default function TagListPage( - tags: TagPage[], - site: "blog" | "slides", - config: Config, -) { - const pageTitle = "タグ一覧"; - - return ( - - - {site === "blog" - ? - : } -
    -
    -

    {pageTitle}

    -
    - {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) => ( - - ))} -
    - - -
    - ); -} diff --git a/services/nuldoc/nuldoc-src/pages/TagPage.ts b/services/nuldoc/nuldoc-src/pages/TagPage.ts new file mode 100644 index 0000000..408f0b4 --- /dev/null +++ b/services/nuldoc/nuldoc-src/pages/TagPage.ts @@ -0,0 +1,50 @@ +import GlobalFooter from "../components/GlobalFooter.ts"; +import BlogGlobalHeader from "../components/BlogGlobalHeader.ts"; +import SlidesGlobalHeader from "../components/SlidesGlobalHeader.ts"; +import PageLayout from "../components/PageLayout.ts"; +import PostPageEntry from "../components/PostPageEntry.ts"; +import SlidePageEntry from "../components/SlidePageEntry.ts"; +import { Config, getTagLabel } from "../config.ts"; +import { getPostPublishedDate } from "../generators/post.ts"; +import { TaggedPage } from "../generators/tagged_page.ts"; +import { elem, Element } from "../dom.ts"; + +export default async function TagPage( + tagSlug: string, + pages: TaggedPage[], + site: "blog" | "slides", + config: Config, +): Promise { + const tagLabel = getTagLabel(config, tagSlug); + const pageTitle = `タグ「${tagLabel}」一覧`; + + const GlobalHeader = site === "blog" ? BlogGlobalHeader : SlidesGlobalHeader; + + return await PageLayout({ + metaCopyrightYear: getPostPublishedDate(pages[pages.length - 1]).year, + metaDescription: `タグ「${tagLabel}」のついた記事またはスライドの一覧`, + metaKeywords: [tagLabel], + metaTitle: `${pageTitle}|${config.sites[site].siteName}`, + metaAtomFeedHref: `https://${ + config.sites[site].fqdn + }/tags/${tagSlug}/atom.xml`, + site, + config, + children: elem( + "body", + { class: "list" }, + GlobalHeader({ config }), + elem( + "main", + { class: "main" }, + elem("header", { class: "page-header" }, elem("h1", {}, pageTitle)), + ...pages.map((page) => + "event" in page + ? SlidePageEntry({ slide: page, config }) + : PostPageEntry({ post: page, config }) + ), + ), + GlobalFooter({ config }), + ), + }); +} diff --git a/services/nuldoc/nuldoc-src/pages/TagPage.tsx b/services/nuldoc/nuldoc-src/pages/TagPage.tsx deleted file mode 100644 index 6c64172..0000000 --- a/services/nuldoc/nuldoc-src/pages/TagPage.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import GlobalFooter from "../components/GlobalFooter.tsx"; -import BlogGlobalHeader from "../components/BlogGlobalHeader.tsx"; -import SlidesGlobalHeader from "../components/SlidesGlobalHeader.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[], - site: "blog" | "slides", - config: Config, -) { - const tagLabel = getTagLabel(config, tagSlug); - const pageTitle = `タグ「${tagLabel}」一覧`; - - return ( - - - {site === "blog" - ? - : } -
    -
    -

    {pageTitle}

    -
    - {pages.map((page) => - "event" in page - ? - : - )} -
    - - -
    - ); -} -- cgit v1.2.3-70-g09d2