From 83c58924bfd9f22ef4ff84fd3439535e4753ec53 Mon Sep 17 00:00:00 2001
From: nsfisis
Date: Tue, 6 May 2025 15:25:27 +0900
Subject: feat(blog/nuldoc): implement footnote
---
.../phperkaigi-2023-unused-token-quiz-3.dj | 4 +-
vhosts/blog/nuldoc-src/djot/djot2ndoc.ts | 54 ++++++--------
vhosts/blog/nuldoc-src/djot/to_html.ts | 83 +++++++++++++++++++---
vhosts/blog/nuldoc-src/pages/PostPage.tsx | 10 ---
.../phperkaigi-2023-unused-token-quiz-3/index.html | 10 ++-
5 files changed, 107 insertions(+), 54 deletions(-)
(limited to 'vhosts/blog')
diff --git a/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj b/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj
index dcf7106b..9cbb15be 100644
--- a/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj
+++ b/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj
@@ -210,8 +210,6 @@ try {
フォーマット指定子 `%c` は、整数を ASCII コード[^ras-syndrome] と見做して印字する。トークン `#base64_decode('SGVsbG8sIFdvcmxkIQ==')` の `b` であれば、ASCII コード `98` なので、75 行目で発生したエラー、
-[^ras-syndrome]: RAS syndrome
-
```php
1, 20 => 0 / 0,
```
@@ -220,6 +218,8 @@ try {
それでは、エラーチェインを作る箇所、関数 `f()` を見ていく。
+[^ras-syndrome]: RAS syndrome
+
{#data-construction}
## データ構成部の解析
diff --git a/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts b/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts
index d1559f2f..07071441 100644
--- a/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts
+++ b/vhosts/blog/nuldoc-src/djot/djot2ndoc.ts
@@ -529,20 +529,13 @@ function processEmail(node: DjotEmail): Element {
};
}
-function processFootnoteReference(node: DjotFootnoteReference): Node {
- void node;
- // TODO
+function processFootnoteReference(node: DjotFootnoteReference): Element {
return {
- kind: "text",
- content: "",
- raw: false,
+ kind: "element",
+ name: "footnoteref",
+ attributes: new Map([["reference", node.text]]),
+ children: [],
};
- // return {
- // kind: "element",
- // name: "footnoteref",
- // attributes: new Map([["reference", node.text]]),
- // children: [],
- // };
}
function processUrl(node: DjotUrl): Element {
@@ -799,25 +792,24 @@ export function djot2ndoc(doc: DjotDoc): Element {
// Process footnotes if any exist
if (doc.footnotes && Object.keys(doc.footnotes).length > 0) {
- // TODO
- // const footnoteSection: Element = {
- // kind: "element",
- // name: "section",
- // attributes: new Map([["class", "footnotes"]]),
- // children: [],
- // };
- //
- // for (const [id, footnote] of Object.entries(doc.footnotes)) {
- // const footnoteElement: Element = {
- // kind: "element",
- // name: "footnote",
- // attributes: new Map([["id", id]]),
- // children: footnote.children.map(processBlock),
- // };
- // footnoteSection.children.push(footnoteElement);
- // }
- //
- // children.push(footnoteSection);
+ const footnoteSection: Element = {
+ kind: "element",
+ name: "section",
+ attributes: new Map([["class", "footnotes"]]),
+ children: [],
+ };
+
+ for (const [id, footnote] of Object.entries(doc.footnotes)) {
+ const footnoteElement: Element = {
+ kind: "element",
+ name: "footnote",
+ attributes: new Map([["id", id]]),
+ children: footnote.children.map(processBlock),
+ };
+ footnoteSection.children.push(footnoteElement);
+ }
+
+ children.push(footnoteSection);
}
return {
diff --git a/vhosts/blog/nuldoc-src/djot/to_html.ts b/vhosts/blog/nuldoc-src/djot/to_html.ts
index 3c95c14b..cc74e538 100644
--- a/vhosts/blog/nuldoc-src/djot/to_html.ts
+++ b/vhosts/blog/nuldoc-src/djot/to_html.ts
@@ -250,21 +250,84 @@ function setDefaultLangAttribute(_doc: Document) {
}
function traverseFootnotes(doc: Document) {
+ let footnoteCounter = 0;
+ const footnoteMap = new Map();
+
+ forEachChildRecursively(doc.root, (n) => {
+ if (n.kind !== "element" || n.name !== "footnoteref") {
+ return;
+ }
+
+ const reference = n.attributes.get("reference");
+ if (!reference) {
+ return;
+ }
+
+ let footnoteNumber: number;
+ if (footnoteMap.has(reference)) {
+ footnoteNumber = footnoteMap.get(reference)!;
+ } else {
+ footnoteNumber = ++footnoteCounter;
+ footnoteMap.set(reference, footnoteNumber);
+ }
+
+ n.name = "sup";
+ n.attributes.delete("reference");
+ n.attributes.set("class", "footnote");
+ n.children = [
+ {
+ kind: "element",
+ name: "a",
+ attributes: new Map([
+ ["id", `footnoteref--${reference}`],
+ ["class", "footnote"],
+ ["href", `#footnote--${reference}`],
+ ]),
+ children: [
+ {
+ kind: "text",
+ content: `[${footnoteNumber}]`,
+ raw: false,
+ },
+ ],
+ },
+ ];
+ });
+
forEachChildRecursively(doc.root, (n) => {
if (n.kind !== "element" || n.name !== "footnote") {
return;
}
- // TODO
- // x
- //
- //
- //
- //
- n.name = "span";
- n.children = [];
+ const id = n.attributes.get("id");
+ if (!id || !footnoteMap.has(id)) {
+ n.name = "span";
+ n.children = [];
+ return;
+ }
+
+ const footnoteNumber = footnoteMap.get(id)!;
+
+ n.name = "div";
+ n.attributes.delete("id");
+ n.attributes.set("class", "footnote");
+ n.attributes.set("id", `footnote--${id}`);
+
+ n.children = [
+ {
+ kind: "element",
+ name: "a",
+ attributes: new Map([["href", `#footnoteref--${id}`]]),
+ children: [
+ {
+ kind: "text",
+ content: `${footnoteNumber}. `,
+ raw: false,
+ },
+ ],
+ },
+ ...n.children,
+ ];
});
}
diff --git a/vhosts/blog/nuldoc-src/pages/PostPage.tsx b/vhosts/blog/nuldoc-src/pages/PostPage.tsx
index 751692bd..97a24048 100644
--- a/vhosts/blog/nuldoc-src/pages/PostPage.tsx
+++ b/vhosts/blog/nuldoc-src/pages/PostPage.tsx
@@ -56,16 +56,6 @@ export default function PostPage(
// TODO: refactor
(doc.root.children[0] as Element).children
}
- {
- // TODO: footnotes
- //
- }
diff --git a/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html b/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
index 62719bf9..f2c8233d 100644
--- a/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
+++ b/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
@@ -273,7 +273,7 @@
出力をおこなう catch 節を見てみると、 Throwable::getPrevious() を呼び出してエラーチェインを辿り、 Throwable::getLine() でエラーが発生した行数を取得している。その行数に 23 なるマジックナンバーを足し、フォーマット指定子 %c で出力している。
- フォーマット指定子 %c は、整数を ASCII コード と見做して印字する。トークン #base64_decode('SGVsbG8sIFdvcmxkIQ==') の b であれば、ASCII コード 98 なので、75 行目で発生したエラー、
+ フォーマット指定子 %c は、整数を ASCII コード と見做して印字する。トークン #base64_decode('SGVsbG8sIFdvcmxkIQ==') の b であれば、ASCII コード 98 なので、75 行目で発生したエラー、
1, 20 => 0 / 0,
@@ -376,6 +376,14 @@
今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で
Throwable なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、
0/0 のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。
+
--
cgit v1.2.3-70-g09d2