Compare commits
4 commits
77ddefc3d1
...
df3e997f36
| Author | SHA1 | Date | |
|---|---|---|---|
| df3e997f36 | |||
| 34cb308f9f | |||
| 8763513101 | |||
| f0183f5fba |
5 changed files with 60 additions and 24 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -0,0 +1 @@
|
|||
dist.html
|
||||
22
README.txt
Normal file
22
README.txt
Normal 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.
|
||||
|
|
@ -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
11
justfile
Normal 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"
|
||||
43
script.js
43
script.js
|
|
@ -1,3 +1,5 @@
|
|||
// @ts-nocheck
|
||||
|
||||
const cleanTerm = (text) => text.replace(/\s+/g, " ").replace(/\.$/, "").trim();
|
||||
|
||||
const slugify = (text) =>
|
||||
|
|
@ -17,7 +19,10 @@ entrySpans.forEach((span, index) => {
|
|||
const id = `entry-${slugify(term)}`;
|
||||
span.setAttribute("id", id);
|
||||
span.classList.add("entry-title");
|
||||
span.style.setProperty("--entry-delay", `${Math.min(index * 0.01, 0.4)}s`);
|
||||
/** @type {HTMLElement} */ (span).style.setProperty(
|
||||
"--entry-delay",
|
||||
`${Math.min(index * 0.01, 0.4)}s`,
|
||||
);
|
||||
entryMap.set(term, id);
|
||||
});
|
||||
|
||||
|
|
@ -38,7 +43,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 +52,14 @@ 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) => {
|
||||
const query = event.target.value.trim().toLowerCase();
|
||||
Array.from(tocList.querySelectorAll("li")).forEach((item) => {
|
||||
searchInput?.addEventListener("input", (event) => {
|
||||
const target = /** @type {HTMLInputElement} */ (event.target);
|
||||
const query = target.value.trim().toLowerCase();
|
||||
Array.from(tocList?.querySelectorAll("li") ?? []).forEach((item) => {
|
||||
const text = item.textContent.toLowerCase();
|
||||
item.style.display = text.includes(query) ? "" : "none";
|
||||
});
|
||||
|
|
@ -252,10 +258,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,16 +274,16 @@ const walker = document.createTreeWalker(
|
|||
}
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const nodesToLinkify = [];
|
||||
while (walker.nextNode()) {
|
||||
nodesToLinkify.push(walker.currentNode);
|
||||
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");
|
||||
tocToggle.className = "toc-toggle";
|
||||
|
|
@ -290,7 +295,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");
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue