summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-05-06 15:25:27 +0900
committernsfisis <nsfisis@gmail.com>2025-05-06 15:25:27 +0900
commit83c58924bfd9f22ef4ff84fd3439535e4753ec53 (patch)
tree90034e4106965433eb587610edfe4356853b96a2
parentce9308b47d5eba482862ae24968c198bbf466e21 (diff)
downloadnsfisis.dev-83c58924bfd9f22ef4ff84fd3439535e4753ec53.tar.gz
nsfisis.dev-83c58924bfd9f22ef4ff84fd3439535e4753ec53.tar.zst
nsfisis.dev-83c58924bfd9f22ef4ff84fd3439535e4753ec53.zip
feat(blog/nuldoc): implement footnote
-rw-r--r--vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj4
-rw-r--r--vhosts/blog/nuldoc-src/djot/djot2ndoc.ts54
-rw-r--r--vhosts/blog/nuldoc-src/djot/to_html.ts83
-rw-r--r--vhosts/blog/nuldoc-src/pages/PostPage.tsx10
-rw-r--r--vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html10
5 files changed, 107 insertions, 54 deletions
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<string, number>();
+
+ 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
- // <footnote>x</footnote>
- //
- // <sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1">1</a>]</sup>
- //
- // <div class="footnote" id="_footnotedef_1">
- // <a href="#_footnoteref_1">1</a>. RAS syndrome
- // </div>
- 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
- // <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>
- }
</div>
</article>
</main>
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 @@
出力をおこなう <code>catch</code> 節を見てみると、 <code>Throwable::getPrevious()</code> を呼び出してエラーチェインを辿り、 <code>Throwable::getLine()</code> でエラーが発生した行数を取得している。その行数に <code>23</code> なるマジックナンバーを足し、フォーマット指定子 <code>%c</code> で出力している。
</p>
<p>
- フォーマット指定子 <code>%c</code> は、整数を ASCII コード と見做して印字する。トークン <code>#base64_decode(&apos;SGVsbG8sIFdvcmxkIQ==&apos;)</code> の <code>b</code> であれば、ASCII コード <code>98</code> なので、75 行目で発生したエラー、
+ フォーマット指定子 <code>%c</code> は、整数を ASCII コード<sup class="footnote"><a class="footnote" href="#footnote--ras-syndrome" id="footnoteref--ras-syndrome">[1]</a></sup> と見做して印字する。トークン <code>#base64_decode(&apos;SGVsbG8sIFdvcmxkIQ==&apos;)</code> の <code>b</code> であれば、ASCII コード <code>98</code> なので、75 行目で発生したエラー、
</p>
<div class="codeblock">
<pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#005CC5">1</span><span style="color:#24292E">, </span><span style="color:#005CC5">20</span><span style="color:#D73A49"> =></span><span style="color:#005CC5"> 0</span><span style="color:#D73A49"> /</span><span style="color:#005CC5"> 0</span><span style="color:#24292E">,</span></span></code></pre>
@@ -376,6 +376,14 @@
今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で <code>Throwable</code> なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、<code>0/0</code> のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。
</p>
</section>
+ <section class="footnotes">
+ <div class="footnote" id="footnote--ras-syndrome">
+ <a href="#footnoteref--ras-syndrome">1. </a>
+ <p>
+ RAS syndrome
+ </p>
+ </div>
+ </section>
</div>
</article>
</main>