aboutsummaryrefslogtreecommitdiffhomepage
path: root/nuldoc-src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2023-03-17 00:48:07 +0900
committernsfisis <nsfisis@gmail.com>2023-03-18 21:02:54 +0900
commit5208a0a0f7e5703af37c9adf6d60f6c8ef10b9ee (patch)
treee2c3272f21e45acb377e70ce2d1c85811e3ab26b /nuldoc-src
parent7688362ad3b57b0cdd6f048d1e595f69748fc183 (diff)
downloadblog.nsfisis.dev-5208a0a0f7e5703af37c9adf6d60f6c8ef10b9ee.tar.gz
blog.nsfisis.dev-5208a0a0f7e5703af37c9adf6d60f6c8ef10b9ee.tar.zst
blog.nsfisis.dev-5208a0a0f7e5703af37c9adf6d60f6c8ef10b9ee.zip
feat(nuldoc): add /slides/ page
Diffstat (limited to 'nuldoc-src')
-rw-r--r--nuldoc-src/commands/build.ts44
-rw-r--r--nuldoc-src/config.ts1
-rw-r--r--nuldoc-src/errors.ts1
-rw-r--r--nuldoc-src/pages/slide.ts133
-rw-r--r--nuldoc-src/pages/slide_list.ts101
-rw-r--r--nuldoc-src/slide/parse.ts19
-rw-r--r--nuldoc-src/slide/slide.ts122
7 files changed, 420 insertions, 1 deletions
diff --git a/nuldoc-src/commands/build.ts b/nuldoc-src/commands/build.ts
index fad5c75..8091938 100644
--- a/nuldoc-src/commands/build.ts
+++ b/nuldoc-src/commands/build.ts
@@ -14,12 +14,17 @@ import {
PostPage,
} from "../pages/post.ts";
import { generatePostListPage } from "../pages/post_list.ts";
+import { generateSlidePage, SlidePage } from "../pages/slide.ts";
+import { generateSlideListPage } from "../pages/slide_list.ts";
import { generateTagPage, TagPage } from "../pages/tag.ts";
import { generateTagListPage } from "../pages/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, config);
await buildTagListPage(tags, config);
await buildHomePage(config);
@@ -28,7 +33,7 @@ export async function runBuildCommand(config: Config) {
await copyStaticFiles(config);
}
-async function buildPostPages(config: 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);
@@ -65,6 +70,43 @@ async function buildPostListPage(posts: PostPage[], config: Config) {
await writePage(postListPage, 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, "**", "*.xml"]);
+ 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), config),
+ );
+ }
+ return slides;
+}
+
+async function buildSlideListPage(slides: SlidePage[], config: Config) {
+ const slideListPage = await generateSlideListPage(slides, config);
+ await writePage(slideListPage, config);
+}
+
async function buildHomePage(config: Config) {
const homePage = await generateHomePage(config);
await writePage(homePage, config);
diff --git a/nuldoc-src/config.ts b/nuldoc-src/config.ts
index 91b4844..52fc6e8 100644
--- a/nuldoc-src/config.ts
+++ b/nuldoc-src/config.ts
@@ -21,6 +21,7 @@ export const config = {
"php": "PHP",
"phpcon": "PHP カンファレンス",
"phperkaigi": "PHPerKaigi",
+ "phpstudy": "PHP 勉強会@東京",
"python": "Python",
"python3": "Python 3",
"ruby": "Ruby",
diff --git a/nuldoc-src/errors.ts b/nuldoc-src/errors.ts
index 4d107aa..ab8052f 100644
--- a/nuldoc-src/errors.ts
+++ b/nuldoc-src/errors.ts
@@ -1,2 +1,3 @@
export class DocBookError extends Error {}
+export class SlideError extends Error {}
export class XmlParseError extends Error {}
diff --git a/nuldoc-src/pages/slide.ts b/nuldoc-src/pages/slide.ts
new file mode 100644
index 0000000..fdfe868
--- /dev/null
+++ b/nuldoc-src/pages/slide.ts
@@ -0,0 +1,133 @@
+import { join } from "std/path/mod.ts";
+import { globalFooter } from "../components/global_footer.ts";
+import { globalHeader } from "../components/global_header.ts";
+import { pageLayout } from "../components/page_layout.ts";
+import { Config } from "../config.ts";
+import { el, text } from "../dom.ts";
+import { Page } from "../page.ts";
+import { Revision } from "../revision.ts";
+import { Slide } from "../slide/slide.ts";
+import { getPostCreatedDate } from "./post.ts";
+
+export interface SlidePage extends Page {
+ title: string;
+ event: string;
+ talkType: string;
+ slideLink: string;
+ tags: string[];
+ revisions: Revision[];
+}
+
+export async function generateSlidePage(
+ slide: Slide,
+ config: Config,
+): Promise<SlidePage> {
+ const body = el(
+ "body",
+ [["class", "single"]],
+ globalHeader(config),
+ el(
+ "main",
+ [["class", "main"]],
+ el(
+ "article",
+ [["class", "post-single"]],
+ el(
+ "header",
+ [["class", "post-header"]],
+ el(
+ "h1",
+ [["class", "post-title"]],
+ text(slide.title),
+ ),
+ ...(slide.tags.length === 0 ? [] : [
+ el(
+ "ul",
+ [["class", "post-tags"]],
+ ...slide.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",
+ [],
+ ...slide.revisions.map((rev) =>
+ el(
+ "li",
+ [["class", "revision"]],
+ el(
+ "time",
+ [["datetime", rev.date]],
+ text(rev.date),
+ ),
+ text(`: ${rev.remark}`),
+ )
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ globalFooter(config),
+ );
+
+ const html = await pageLayout(
+ {
+ metaCopyrightYear: parseInt(
+ getPostCreatedDate(slide).substring(0, 4),
+ ),
+ metaDescription: slide.title,
+ metaKeywords: slide.tags.map((slug) =>
+ (config.blog.tagLabels as { [key: string]: string })[slug]
+ ),
+ metaTitle: `${slide.event} (${slide.talkType}) | ${config.blog.siteName}`,
+ requiresSyntaxHighlight: true,
+ },
+ body,
+ config,
+ );
+
+ const cwd = Deno.cwd();
+ const contentDir = join(cwd, config.locations.contentDir);
+ const destFilePath = join(
+ slide.sourceFilePath.replace(contentDir, "").replace(".xml", ""),
+ "index.html",
+ );
+ return {
+ root: el("__root__", [], html),
+ renderer: "html",
+ destFilePath: destFilePath,
+ href: destFilePath.replace("index.html", ""),
+ title: slide.title,
+ event: slide.event,
+ talkType: slide.talkType,
+ slideLink: slide.slideLink,
+ tags: slide.tags,
+ revisions: slide.revisions,
+ };
+}
diff --git a/nuldoc-src/pages/slide_list.ts b/nuldoc-src/pages/slide_list.ts
new file mode 100644
index 0000000..61a2764
--- /dev/null
+++ b/nuldoc-src/pages/slide_list.ts
@@ -0,0 +1,101 @@
+import { globalFooter } from "../components/global_footer.ts";
+import { globalHeader } from "../components/global_header.ts";
+import { pageLayout } from "../components/page_layout.ts";
+import { Config } from "../config.ts";
+import { el, text } from "../dom.ts";
+import { Page } from "../page.ts";
+import { getPostCreatedDate, getPostUpdatedDate } from "./post.ts";
+import { SlidePage } from "./slide.ts";
+
+export type SlideListPage = Page;
+
+export async function generateSlideListPage(
+ slides: SlidePage[],
+ config: Config,
+): Promise<SlideListPage> {
+ const pageTitle = "スライド一覧";
+
+ const body = el(
+ "body",
+ [["class", "list"]],
+ globalHeader(config),
+ el(
+ "main",
+ [["class", "main"]],
+ el(
+ "header",
+ [["class", "page-header"]],
+ el(
+ "h1",
+ [],
+ text(pageTitle),
+ ),
+ ),
+ ...Array.from(slides).sort((a, b) => {
+ const ta = getPostCreatedDate(a);
+ const tb = getPostCreatedDate(b);
+ if (ta > tb) return -1;
+ if (ta < tb) return 1;
+ return 0;
+ }).map((slide) =>
+ el(
+ "article",
+ [["class", "post-entry"]],
+ el(
+ "a",
+ [["href", slide.href]],
+ el(
+ "header",
+ [["class", "entry-header"]],
+ el("h2", [], text(`${slide.event} (${slide.talkType})`)),
+ ),
+ el(
+ "section",
+ [["class", "entry-content"]],
+ el("p", [], text(slide.title)),
+ ),
+ el(
+ "footer",
+ [["class", "entry-footer"]],
+ text("Posted on "),
+ el(
+ "time",
+ [["datetime", getPostCreatedDate(slide)]],
+ text(getPostCreatedDate(slide)),
+ ),
+ ...(slide.revisions.length > 1
+ ? [
+ text(", updated on "),
+ el("time", [[
+ "datetime",
+ getPostUpdatedDate(slide),
+ ]], text(getPostUpdatedDate(slide))),
+ ]
+ : []),
+ ),
+ ),
+ )
+ ),
+ ),
+ globalFooter(config),
+ );
+
+ const html = await pageLayout(
+ {
+ metaCopyrightYear: config.blog.siteCopyrightYear,
+ metaDescription: "登壇したイベントで使用したスライドの一覧",
+ metaKeywords: [],
+ metaTitle: `${pageTitle} | ${config.blog.siteName}`,
+ requiresSyntaxHighlight: false,
+ },
+ body,
+ config,
+ );
+
+ return {
+ root: el("__root__", [], html),
+ renderer: "html",
+ destFilePath: "/slides/index.html",
+ href: "/slides/",
+ };
+}
diff --git a/nuldoc-src/slide/parse.ts b/nuldoc-src/slide/parse.ts
new file mode 100644
index 0000000..00ff645
--- /dev/null
+++ b/nuldoc-src/slide/parse.ts
@@ -0,0 +1,19 @@
+import { Config } from "../config.ts";
+import { parseXmlFile } from "../xml.ts";
+import { SlideError, XmlParseError } from "../errors.ts";
+import { createNewSlideFromRootElement, Slide } from "./slide.ts";
+
+export async function parseSlideFile(
+ filePath: string,
+ config: Config,
+): Promise<Slide> {
+ try {
+ const root = await parseXmlFile(filePath);
+ return createNewSlideFromRootElement(root, filePath, config);
+ } catch (e) {
+ if (e instanceof SlideError || e instanceof XmlParseError) {
+ e.message = `${e.message} in ${filePath}`;
+ }
+ throw e;
+ }
+}
diff --git a/nuldoc-src/slide/slide.ts b/nuldoc-src/slide/slide.ts
new file mode 100644
index 0000000..859bd56
--- /dev/null
+++ b/nuldoc-src/slide/slide.ts
@@ -0,0 +1,122 @@
+import { Config } from "../config.ts";
+import { SlideError } from "../errors.ts";
+import { Revision } from "../revision.ts";
+import {
+ Element,
+ findChildElements,
+ findFirstChildElement,
+ innerText,
+} from "../dom.ts";
+
+export type Slide = {
+ sourceFilePath: string;
+ title: string;
+ event: string;
+ talkType: string;
+ slideLink: string;
+ tags: string[];
+ revisions: Revision[];
+};
+
+export function createNewSlideFromRootElement(
+ root: Element,
+ sourceFilePath: string,
+ _config: Config,
+): Slide {
+ const slide = findFirstChildElement(root, "slide");
+ if (!slide) {
+ throw new SlideError(
+ `[slide.new] <slide> element not found`,
+ );
+ }
+ const info = findFirstChildElement(slide, "info");
+ if (!info) {
+ throw new SlideError(
+ `[slide.new] <info> element not found`,
+ );
+ }
+
+ const titleElement = findFirstChildElement(info, "title");
+ if (!titleElement) {
+ throw new SlideError(
+ `[slide.new] <title> element not found`,
+ );
+ }
+ const title = innerText(titleElement).trim();
+
+ const eventElement = findFirstChildElement(info, "event");
+ if (!eventElement) {
+ throw new SlideError(
+ `[slide.new] <event> element not found`,
+ );
+ }
+ const event = innerText(eventElement).trim();
+
+ const talkTypeElement = findFirstChildElement(info, "talktype");
+ if (!talkTypeElement) {
+ throw new SlideError(
+ `[slide.new] <talktype> element not found`,
+ );
+ }
+ const talkType = innerText(talkTypeElement).trim();
+
+ const slideLinkElement = findFirstChildElement(info, "link");
+ if (!slideLinkElement) {
+ throw new SlideError(
+ `[slide.new] <link> element not found`,
+ );
+ }
+ const slideLink = innerText(slideLinkElement).trim();
+
+ const keywordsetElement = findFirstChildElement(info, "keywordset");
+ let tags: string[];
+ if (!keywordsetElement) {
+ tags = [];
+ } else {
+ tags = findChildElements(keywordsetElement, "keyword").map((x) =>
+ innerText(x).trim()
+ );
+ }
+ const revhistoryElement = findFirstChildElement(info, "revhistory");
+ if (!revhistoryElement) {
+ throw new SlideError(
+ `[slide.new] <revhistory> element not found`,
+ );
+ }
+ const revisions = findChildElements(revhistoryElement, "revision").map(
+ (x, i) => {
+ const dateElement = findFirstChildElement(x, "date");
+ if (!dateElement) {
+ throw new SlideError(
+ `[slide.new] <date> element not found`,
+ );
+ }
+ const revremarkElement = findFirstChildElement(x, "revremark");
+ if (!revremarkElement) {
+ throw new SlideError(
+ `[slide.new] <revremark> element not found`,
+ );
+ }
+ return {
+ number: i + 1,
+ date: innerText(dateElement).trim(),
+ remark: innerText(revremarkElement).trim(),
+ };
+ },
+ );
+ if (revisions.length === 0) {
+ throw new SlideError(
+ `[slide.new] <revision> element not found`,
+ );
+ }
+
+ return {
+ sourceFilePath: sourceFilePath,
+ title: title,
+ event: event,
+ talkType: talkType,
+ slideLink: slideLink,
+ tags: tags,
+ revisions: revisions,
+ };
+}