diff options
Diffstat (limited to 'nuldoc-src/pages')
| -rw-r--r-- | nuldoc-src/pages/about.ts | 113 | ||||
| -rw-r--r-- | nuldoc-src/pages/post.ts | 207 | ||||
| -rw-r--r-- | nuldoc-src/pages/post_list.ts | 152 | ||||
| -rw-r--r-- | nuldoc-src/pages/tag.ts | 146 | ||||
| -rw-r--r-- | nuldoc-src/pages/utils.ts | 51 |
5 files changed, 669 insertions, 0 deletions
diff --git a/nuldoc-src/pages/about.ts b/nuldoc-src/pages/about.ts new file mode 100644 index 0000000..bba4031 --- /dev/null +++ b/nuldoc-src/pages/about.ts @@ -0,0 +1,113 @@ +import { Config } from "../config.ts"; +import { Page } from "../page.ts"; +import { + el, + linkElement, + metaElement, + stylesheetLinkElement, + text, +} from "./utils.ts"; + +export type AboutPage = Page; + +export async function generateAboutPage(config: Config): Promise<AboutPage> { + const head = el( + "head", + [], + metaElement([["charset", "UTF-8"]]), + metaElement([["name", "viewport"], [ + "content", + "width=device-width, initial-scale=1.0", + ]]), + metaElement([["name", "author"], ["content", config.blog.author]]), + metaElement([["name", "copyright"], [ + "content", + `© ${config.blog.siteCopyrightYear} ${config.blog.author}`, + ]]), + metaElement([["name", "description"], [ + "content", + "このサイトの著者について", + ]]), + linkElement("icon", "/favicon.svg", "image/svg+xml"), + el("title", [], text(`About | ${config.blog.siteName}`)), + await stylesheetLinkElement("/style.css", config), + ); + const body = el( + "body", + [["class", "single"]], + el( + "header", + [["class", "header"]], + el( + "nav", + [["class", "nav"]], + el( + "ul", + [], + el( + "li", + [["class", "logo"]], + el("a", [["href", "/"]], text(config.blog.siteName)), + ), + el( + "li", + [], + el("a", [["href", "/about"]], text("About")), + ), + el( + "li", + [], + el("a", [["href", "/posts"]], text("Posts")), + ), + el( + "li", + [], + el("a", [["href", "/slides"]], text("Slides")), + ), + ), + ), + ), + el( + "main", + [["class", "main"]], + el( + "article", + [["class", "post-single"]], + el( + "header", + [["class", "post-header"]], + el( + "h1", + [["class", "post-title"]], + text("About"), + ), + ), + el( + "div", + [["class", "post-content"]], + text("WIP"), + ), + ), + ), + el( + "footer", + [["class", "footer"]], + text( + `© ${config.blog.siteCopyrightYear} ${config.blog.author}`, + ), + ), + ); + const html = el( + "html", + [["lang", "ja-JP"]], + head, + body, + ); + + return { + root: el("__root__", [], html), + renderer: "html", + destFilePath: "/about/index.html", + href: "/about/", + }; +} diff --git a/nuldoc-src/pages/post.ts b/nuldoc-src/pages/post.ts new file mode 100644 index 0000000..a431c37 --- /dev/null +++ b/nuldoc-src/pages/post.ts @@ -0,0 +1,207 @@ +import { join } from "std/path/mod.ts"; +import { Config } from "../config.ts"; +import { Element } from "../dom.ts"; +import { Document } from "../docbook/document.ts"; +import { Page } from "../page.ts"; +import { Revision } from "../revision.ts"; +import { + el, + linkElement, + metaElement, + stylesheetLinkElement, + text, +} from "./utils.ts"; + +export interface PostPage extends Page { + title: string; + summary: string; + tags: string[]; + revisions: Revision[]; +} + +export function getPostCreatedDate(page: { revisions: Revision[] }): string { + return page.revisions[0].date; +} + +export function getPostUpdatedDate(page: { revisions: Revision[] }): string { + return page.revisions[page.revisions.length - 1].date; +} + +export async function generatePostPage( + doc: Document, + config: Config, +): Promise<PostPage> { + const headChildren = [ + metaElement([["charset", "UTF-8"]]), + metaElement([["name", "viewport"], [ + "content", + "width=device-width, initial-scale=1.0", + ]]), + metaElement([["name", "author"], ["content", config.blog.author]]), + metaElement([["name", "copyright"], [ + "content", + `© ${getPostCreatedDate(doc).substring(0, 4)} ${config.blog.author}`, + ]]), + metaElement([["name", "description"], ["content", doc.summary]]), + ]; + if (doc.tags.length !== 0) { + headChildren.push( + metaElement([["name", "keywords"], [ + "content", + doc.tags.map((slug) => + (config.blog.tagLabels as { [key: string]: string })[slug] + ).join(","), + ]]), + ); + } + headChildren.push(linkElement("icon", "/favicon.svg", "image/svg+xml")); + headChildren.push( + el("title", [], text(`${doc.title} | ${config.blog.siteName}`)), + ); + headChildren.push(await stylesheetLinkElement("/style.css", config)); + headChildren.push(await stylesheetLinkElement("/hl.css", config)); + const head = el("head", [], ...headChildren); + const body = el( + "body", + [["class", "single"]], + el( + "header", + [["class", "header"]], + el( + "nav", + [["class", "nav"]], + el( + "ul", + [], + el( + "li", + [["class", "logo"]], + el("a", [["href", "/"]], text(config.blog.siteName)), + ), + el( + "li", + [], + el("a", [["href", "/about"]], text("About")), + ), + el( + "li", + [], + el("a", [["href", "/posts"]], text("Posts")), + ), + el( + "li", + [], + el("a", [["href", "/slides"]], text("Slides")), + ), + ), + ), + ), + el( + "main", + [["class", "main"]], + el( + "article", + [["class", "post-single"]], + el( + "header", + [["class", "post-header"]], + el( + "h1", + [["class", "post-title"]], + text(doc.title), + ), + ...(doc.tags.length === 0 ? [] : [ + el( + "ul", + [["class", "post-tags"]], + ...doc.tags.map((slug) => + el( + "li", + [["class", "tag"]], + el( + "a", + [["href", `/tags/${slug}`]], + text( + (config.blog.tagLabels as { + [key: string]: string; + })[slug], + ), + ), + ) + ), + ), + ]), + ), + el( + "div", + [["class", "post-content"]], + el( + "section", + [], + el( + "h2", + [["id", "changelog"]], + text("更新履歴"), + ), + el( + "ol", + [], + ...doc.revisions.map((rev) => + el( + "li", + [["class", "revision"]], + el( + "time", + [["datetime", rev.date]], + text(rev.date), + ), + text(`: ${rev.remark}`), + ) + ), + ), + ), + // TODO: refactor + ...(doc.root.children[0] as Element).children, + // TODO: footnotes + // <div id="footnotes"> + // <% for footnote in footnotes %> + // <div class="footnote" id="_footnotedef_<%= footnote.index %>"> + // <a href="#_footnoteref_<%= footnote.index %>"><%= footnote.index %></a>. <%= footnote.text %> + // </div> + // <% end %> + // </div> + ), + ), + ), + el( + "footer", + [["class", "footer"]], + text( + `© ${config.blog.siteCopyrightYear} ${config.blog.author}`, + ), + ), + ); + const html = el( + "html", + [["lang", "ja-JP"]], + head, + body, + ); + + const cwd = Deno.cwd(); + const contentDir = join(cwd, config.locations.contentDir); + const destFilePath = join( + doc.sourceFilePath.replace(contentDir, "").replace(".xml", ""), + "index.html", + ); + return { + root: el("__root__", [], html), + renderer: "html", + destFilePath: destFilePath, + href: destFilePath.replace("index.html", ""), + title: doc.title, + summary: doc.summary, + tags: doc.tags, + revisions: doc.revisions, + }; +} diff --git a/nuldoc-src/pages/post_list.ts b/nuldoc-src/pages/post_list.ts new file mode 100644 index 0000000..e22316e --- /dev/null +++ b/nuldoc-src/pages/post_list.ts @@ -0,0 +1,152 @@ +import { Config } from "../config.ts"; +import { Page } from "../page.ts"; +import { getPostCreatedDate, getPostUpdatedDate, PostPage } from "./post.ts"; +import { + el, + linkElement, + metaElement, + stylesheetLinkElement, + text, +} from "./utils.ts"; + +export type PostListPage = Page; + +export async function generatePostListPage( + posts: PostPage[], + config: Config, +): Promise<PostListPage> { + const pageTitle = "投稿一覧"; + + const head = el( + "head", + [], + metaElement([["charset", "UTF-8"]]), + metaElement([["name", "viewport"], [ + "content", + "width=device-width, initial-scale=1.0", + ]]), + metaElement([["name", "author"], ["content", config.blog.author]]), + metaElement([["name", "copyright"], [ + "content", + `© ${config.blog.siteCopyrightYear} ${config.blog.author}`, + ]]), + metaElement([["name", "description"], ["content", "投稿した記事の一覧"]]), + linkElement("icon", "/favicon.svg", "image/svg+xml"), + el("title", [], text(`${pageTitle} | ${config.blog.siteName}`)), + await stylesheetLinkElement("/style.css", config), + ); + const body = el( + "body", + [["class", "list"]], + el( + "header", + [["class", "header"]], + el( + "nav", + [["class", "nav"]], + el( + "ul", + [], + el( + "li", + [["class", "logo"]], + el("a", [["href", "/"]], text(config.blog.siteName)), + ), + el( + "li", + [], + el("a", [["href", "/about"]], text("About")), + ), + el( + "li", + [], + el("a", [["href", "/posts"]], text("Posts")), + ), + el( + "li", + [], + el("a", [["href", "/slides"]], text("Slides")), + ), + ), + ), + ), + el( + "main", + [["class", "main"]], + el( + "header", + [["class", "page-header"]], + el( + "h1", + [], + text(pageTitle), + ), + ), + ...Array.from(posts).sort((a, b) => { + const ta = getPostCreatedDate(a); + const tb = getPostCreatedDate(b); + if (ta > tb) return -1; + if (ta < tb) return 1; + return 0; + }).map((post) => + el( + "article", + [["class", "post-entry"]], + el( + "a", + [["href", post.href]], + el( + "header", + [["class", "entry-header"]], + el("h2", [], text(post.title)), + ), + el( + "section", + [["class", "entry-content"]], + el("p", [], text(post.summary)), + ), + el( + "footer", + [["class", "entry-footer"]], + text("Posted on"), + el( + "time", + [["datetime", getPostCreatedDate(post)]], + text(getPostCreatedDate(post)), + ), + ...(post.revisions.length > 1 + ? [ + text(", updated on "), + el("time", [[ + "datetime", + getPostUpdatedDate(post), + ]], text(getPostUpdatedDate(post))), + ] + : []), + ), + ), + ) + ), + ), + el( + "footer", + [["class", "footer"]], + text( + `© ${config.blog.siteCopyrightYear} ${config.blog.author}`, + ), + ), + ); + const html = el( + "html", + [["lang", "ja-JP"]], + head, + body, + ); + + return { + root: el("__root__", [], html), + renderer: "html", + destFilePath: "/posts/index.html", + href: "/posts/", + }; +} diff --git a/nuldoc-src/pages/tag.ts b/nuldoc-src/pages/tag.ts new file mode 100644 index 0000000..51aedcd --- /dev/null +++ b/nuldoc-src/pages/tag.ts @@ -0,0 +1,146 @@ +import { Config } from "../config.ts"; +import { Page } from "../page.ts"; +import { getPostCreatedDate, getPostUpdatedDate, PostPage } from "./post.ts"; +import { + el, + linkElement, + metaElement, + stylesheetLinkElement, + text, +} from "./utils.ts"; + +export type TagPage = Page; + +export async function generateTagPage( + tagSlug: string, + posts: PostPage[], + config: Config, +): Promise<TagPage> { + const tagLabel = + (config.blog.tagLabels as { [key: string]: string })[tagSlug]; + const pageTitle = `タグ「${tagLabel}」一覧`; + + const headChildren = [ + metaElement([["charset", "UTF-8"]]), + metaElement([["name", "viewport"], [ + "content", + "width=device-width, initial-scale=1.0", + ]]), + metaElement([["name", "author"], ["content", config.blog.author]]), + metaElement([["name", "copyright"], [ + "content", + `© ${ + getPostCreatedDate(posts[posts.length - 1]).substring(0, 4) + } ${config.blog.author}`, + ]]), + metaElement([["name", "description"], [ + "content", + `タグ「${tagLabel}」のついた記事一覧`, + ]]), + metaElement([["name", "keywords"], ["content", tagLabel]]), + linkElement("icon", "/favicon.svg", "image/svg+xml"), + el("title", [], text(`${pageTitle} | ${config.blog.siteName}`)), + await stylesheetLinkElement("/style.css", config), + ]; + const head = el("head", [], ...headChildren); + const body = el( + "body", + [["class", "list"]], + el( + "header", + [["class", "header"]], + el( + "nav", + [["class", "nav"]], + el( + "ul", + [], + el( + "li", + [["class", "logo"]], + el("a", [["href", "/"]], text(config.blog.siteName)), + ), + el( + "li", + [], + el("a", [["href", "/about"]], text("About")), + ), + el( + "li", + [], + el("a", [["href", "/posts"]], text("Posts")), + ), + el( + "li", + [], + el("a", [["href", "/slides"]], text("Slides")), + ), + ), + ), + ), + el( + "main", + [["class", "main"]], + el("header", [["class", "page-header"]], el("h1", [], text(pageTitle))), + ...posts.map((post) => + el( + "article", + [["class", "post-entry"]], + el( + "a", + [["href", post.href]], + el( + "header", + [["class", "entry-header"]], + el("h2", [], text(post.title)), + ), + el( + "section", + [["class", "entry-content"]], + el("p", [], text(post.summary)), + ), + el( + "footer", + [["class", "entry-footer"]], + text("Posted on"), + el( + "time", + [["datetime", getPostCreatedDate(post)]], + text(getPostCreatedDate(post)), + ), + ...(post.revisions.length > 1 + ? [ + text(", updated on "), + el("time", [[ + "datetime", + getPostUpdatedDate(post), + ]], text(getPostUpdatedDate(post))), + ] + : []), + ), + ), + ) + ), + ), + el( + "footer", + [["class", "footer"]], + text( + `© ${config.blog.siteCopyrightYear} ${config.blog.author}`, + ), + ), + ); + const html = el( + "html", + [["lang", "ja-JP"]], + head, + body, + ); + + return { + root: el("__root__", [], html), + renderer: "html", + destFilePath: `/tags/${tagSlug}/index.html`, + href: `/tags/${tagSlug}/`, + }; +} diff --git a/nuldoc-src/pages/utils.ts b/nuldoc-src/pages/utils.ts new file mode 100644 index 0000000..018c460 --- /dev/null +++ b/nuldoc-src/pages/utils.ts @@ -0,0 +1,51 @@ +import { join } from "std/path/mod.ts"; +import { crypto, toHashString } from "std/crypto/mod.ts"; +import { Element, Node, Text } from "../dom.ts"; +import { Config } from "../config.ts"; + +export function text(content: string): Text { + return { + kind: "text", + content: content, + raw: false, + }; +} + +export function el( + name: string, + attrs: [string, string][], + ...children: Node[] +): Element { + return { + kind: "element", + name: name, + attributes: new Map(attrs), + children: children, + }; +} + +export async function stylesheetLinkElement( + fileName: string, + config: Config, +): Promise<Element> { + const filePath = join(Deno.cwd(), config.locations.staticDir, fileName); + const content = (await Deno.readFile(filePath)).buffer; + const hash = toHashString(await crypto.subtle.digest("MD5", content), "hex"); + return el("link", [["rel", "stylesheet"], ["href", `${fileName}?h=${hash}`]]); +} + +export function metaElement(attrs: [string, string][]): Element { + return el("meta", attrs); +} + +export function linkElement( + rel: string, + href: string, + type: string | null, +): Element { + const attrs: [string, string][] = [["rel", rel], ["href", href]]; + if (type !== null) { + attrs.push(["type", type]); + } + return el("link", attrs); +} |
