Compare commits

...

4 commits

5 changed files with 54 additions and 22 deletions

1
.gitignore vendored
View file

@ -0,0 +1 @@
dist.html

22
README.txt Normal file
View file

@ -0,0 +1,22 @@
the tough guide to fantasyland
===============================
an interactive web version of diana wynne jones' satirical encyclopedia
of fantasy tropes. click any ALL CAPS term to jump to its definition.
features
--------
- auto-linking of capitalized terms to their definitions
- searchable sidebar with alphabetical navigation
- collapsible table of contents
- handles plurals, special cases, and alternate spellings
files
-----
index.html - the book content (converted from epub via calibre/tidy)
script.js - term detection, linking, toc generation
style.css - parchment aesthetic
usage
-----
just open index.html in a browser. no build step.

View file

@ -8,7 +8,7 @@
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>The Tough Guide to Fantasyland</title>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="style.css" inline />
</head>
<body>
<div class="page">
@ -17597,9 +17597,6 @@
</main>
</div>
</div>
<script
type="text/javascript"
src="script.js"
></script>
<script type="text/javascript" src="script.js" inline></script>
</body>
</html>

11
justfile Normal file
View file

@ -0,0 +1,11 @@
deploy_dir := "/var/www/html/dungeon.red/fantasyland"
default: deploy
compile:
bunx inline-source-cli index.html dist.html
deploy: compile
mkdir -p {{deploy_dir}}
cp dist.html {{deploy_dir}}/index.html
@echo "deployed to {{deploy_dir}}/index.html"

View file

@ -1,3 +1,5 @@
// @ts-nocheck
const cleanTerm = (text) => text.replace(/\s+/g, " ").replace(/\.$/, "").trim();
const slugify = (text) =>
@ -38,7 +40,7 @@ termEntries.forEach(([term, id]) => {
link.textContent = term;
const item = document.createElement("li");
item.appendChild(link);
tocList.appendChild(item);
tocList?.appendChild(item);
});
Array.from(firstByLetter.entries())
@ -47,13 +49,13 @@ Array.from(firstByLetter.entries())
const link = document.createElement("a");
link.href = `#${id}`;
link.textContent = letter;
tocLetters.appendChild(link);
tocLetters?.appendChild(link);
});
const searchInput = document.getElementById("toc-search");
searchInput.addEventListener("input", (event) => {
searchInput?.addEventListener("input", (event) => {
const query = event.target.value.trim().toLowerCase();
Array.from(tocList.querySelectorAll("li")).forEach((item) => {
Array.from(tocList?.querySelectorAll("li") ?? []).forEach((item) => {
const text = item.textContent.toLowerCase();
item.style.display = text.includes(query) ? "" : "none";
});
@ -252,10 +254,9 @@ const linkifyTextNode = (node) => {
node.parentNode.replaceChild(fragment, node);
};
const walker = document.createTreeWalker(
document.getElementById("content"),
NodeFilter.SHOW_TEXT,
{
const contentEl = document.getElementById("content");
if (contentEl) {
const walker = document.createTreeWalker(contentEl, NodeFilter.SHOW_TEXT, {
acceptNode: (node) => {
const parent = node.parentElement;
if (!parent) {
@ -269,15 +270,15 @@ const walker = document.createTreeWalker(
}
return NodeFilter.FILTER_ACCEPT;
},
},
);
});
const nodesToLinkify = [];
while (walker.nextNode()) {
const nodesToLinkify = [];
while (walker.nextNode()) {
nodesToLinkify.push(walker.currentNode);
}
}
nodesToLinkify.forEach((node) => linkifyTextNode(node));
nodesToLinkify.forEach((node) => linkifyTextNode(node));
}
// TOC collapse/expand toggle
const tocToggle = document.createElement("button");
@ -290,7 +291,7 @@ const toc = document.querySelector(".toc");
const layout = document.querySelector(".layout");
tocToggle.addEventListener("click", () => {
toc.classList.toggle("collapsed");
layout.classList.toggle("toc-hidden");
toc?.classList.toggle("collapsed");
layout?.classList.toggle("toc-hidden");
document.body.classList.toggle("toc-collapsed");
});