aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--content/slides/2023-01-18/phpstudy-tokyo-148.xml23
-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
-rw-r--r--public/slides/2023-01-18/phpstudy-tokyo-148/index.html66
-rw-r--r--public/slides/index.html58
10 files changed, 567 insertions, 1 deletions
diff --git a/content/slides/2023-01-18/phpstudy-tokyo-148.xml b/content/slides/2023-01-18/phpstudy-tokyo-148.xml
new file mode 100644
index 0000000..cb58e03
--- /dev/null
+++ b/content/slides/2023-01-18/phpstudy-tokyo-148.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<slide>
+ <info>
+ <title>明日のあなたの役に立たない PHP コーディング技法~polyglot~</title>
+ <event>
+ PHP 勉強会@東京 第148 回
+ </event>
+ <talktype>
+ LT
+ </talktype>
+ <link>https://github.com/nsfisis/PHPStudy148-slide/blob/main/slide.pdf</link>
+ <keywordset>
+ <keyword>php</keyword>
+ <keyword>phpstudy</keyword>
+ </keywordset>
+ <revhistory>
+ <revision>
+ <date>2023-01-18</date>
+ <revremark>登壇</revremark>
+ </revision>
+ </revhistory>
+ </info>
+</slide>
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,
+ };
+}
diff --git a/public/slides/2023-01-18/phpstudy-tokyo-148/index.html b/public/slides/2023-01-18/phpstudy-tokyo-148/index.html
new file mode 100644
index 0000000..192c6fe
--- /dev/null
+++ b/public/slides/2023-01-18/phpstudy-tokyo-148/index.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="ja-JP">
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="author" content="nsfisis">
+ <meta name="copyright" content="&copy; 2023 nsfisis">
+ <meta name="description" content="明日のあなたの役に立たない PHP コーディング技法~polyglot~">
+ <meta name="keywords" content="PHP,PHP 勉強会@東京">
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
+ <title>PHP 勉強会@東京 第148 回 (LT) | REPL: Rest-Eat-Program Loop</title>
+ <link rel="stylesheet" href="/style.css?h=48694677b43b77e5c45f25e6bfdebb41">
+ <link rel="stylesheet" href="/hl.css?h=340e65ffd5c17713efc9107c06304f7b">
+ </head>
+ <body class="single">
+ <header class="header">
+ <nav class="nav">
+ <ul>
+ <li class="logo">
+ <a href="/">REPL: Rest-Eat-Program Loop</a>
+ </li>
+ <li>
+ <a href="/about/">About</a>
+ </li>
+ <li>
+ <a href="/posts/">Posts</a>
+ </li>
+ <li>
+ <a href="/slides/">Slides</a>
+ </li>
+ <li>
+ <a href="/tags/">Tags</a>
+ </li>
+ </ul>
+ </nav>
+ </header>
+ <main class="main">
+ <article class="post-single">
+ <header class="post-header">
+ <h1 class="post-title">明日のあなたの役に立たない PHP コーディング技法~polyglot~</h1>
+ <ul class="post-tags">
+ <li class="tag">
+ <a href="/tags/php/">PHP</a>
+ </li>
+ <li class="tag">
+ <a href="/tags/phpstudy/">PHP 勉強会@東京</a>
+ </li>
+ </ul>
+ </header>
+ <div class="post-content">
+ <section>
+ <h2 id="changelog">更新履歴</h2>
+ <ol>
+ <li class="revision">
+ <time datetime="2023-01-18">2023-01-18</time>: 登壇
+ </li>
+ </ol>
+ </section>
+ </div>
+ </article>
+ </main>
+ <footer class="footer">
+ &copy; 2021 nsfisis
+ </footer>
+ </body>
+</html>
diff --git a/public/slides/index.html b/public/slides/index.html
new file mode 100644
index 0000000..5d89c35
--- /dev/null
+++ b/public/slides/index.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html lang="ja-JP">
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="author" content="nsfisis">
+ <meta name="copyright" content="&copy; 2021 nsfisis">
+ <meta name="description" content="登壇したイベントで使用したスライドの一覧">
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
+ <title>スライド一覧 | REPL: Rest-Eat-Program Loop</title>
+ <link rel="stylesheet" href="/style.css?h=48694677b43b77e5c45f25e6bfdebb41">
+ </head>
+ <body class="list">
+ <header class="header">
+ <nav class="nav">
+ <ul>
+ <li class="logo">
+ <a href="/">REPL: Rest-Eat-Program Loop</a>
+ </li>
+ <li>
+ <a href="/about/">About</a>
+ </li>
+ <li>
+ <a href="/posts/">Posts</a>
+ </li>
+ <li>
+ <a href="/slides/">Slides</a>
+ </li>
+ <li>
+ <a href="/tags/">Tags</a>
+ </li>
+ </ul>
+ </nav>
+ </header>
+ <main class="main">
+ <header class="page-header">
+ <h1>スライド一覧</h1>
+ </header>
+ <article class="post-entry">
+ <a href="/slides/2023-01-18/phpstudy-tokyo-148/"> <header class="entry-header">
+ <h2>PHP 勉強会@東京 第148 回 (LT)</h2>
+ </header>
+ <section class="entry-content">
+ <p>
+ 明日のあなたの役に立たない PHP コーディング技法~polyglot~
+ </p>
+ </section>
+ <footer class="entry-footer">
+ Posted on <time datetime="2023-01-18">2023-01-18</time>
+ </footer>
+</a>
+ </article>
+ </main>
+ <footer class="footer">
+ &copy; 2021 nsfisis
+ </footer>
+ </body>
+</html>