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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>The Tough Guide to Fantasyland</title>
|
<title>The Tough Guide to Fantasyland</title>
|
||||||
<link rel="stylesheet" href="style.css" />
|
<link rel="stylesheet" href="style.css" inline />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
|
|
@ -17597,9 +17597,6 @@
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script
|
<script type="text/javascript" src="script.js" inline></script>
|
||||||
type="text/javascript"
|
|
||||||
src="script.js"
|
|
||||||
></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 cleanTerm = (text) => text.replace(/\s+/g, " ").replace(/\.$/, "").trim();
|
||||||
|
|
||||||
const slugify = (text) =>
|
const slugify = (text) =>
|
||||||
|
|
@ -17,7 +19,10 @@ entrySpans.forEach((span, index) => {
|
||||||
const id = `entry-${slugify(term)}`;
|
const id = `entry-${slugify(term)}`;
|
||||||
span.setAttribute("id", id);
|
span.setAttribute("id", id);
|
||||||
span.classList.add("entry-title");
|
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);
|
entryMap.set(term, id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -38,7 +43,7 @@ termEntries.forEach(([term, id]) => {
|
||||||
link.textContent = term;
|
link.textContent = term;
|
||||||
const item = document.createElement("li");
|
const item = document.createElement("li");
|
||||||
item.appendChild(link);
|
item.appendChild(link);
|
||||||
tocList.appendChild(item);
|
tocList?.appendChild(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
Array.from(firstByLetter.entries())
|
Array.from(firstByLetter.entries())
|
||||||
|
|
@ -47,13 +52,14 @@ Array.from(firstByLetter.entries())
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = `#${id}`;
|
link.href = `#${id}`;
|
||||||
link.textContent = letter;
|
link.textContent = letter;
|
||||||
tocLetters.appendChild(link);
|
tocLetters?.appendChild(link);
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchInput = document.getElementById("toc-search");
|
const searchInput = document.getElementById("toc-search");
|
||||||
searchInput.addEventListener("input", (event) => {
|
searchInput?.addEventListener("input", (event) => {
|
||||||
const query = event.target.value.trim().toLowerCase();
|
const target = /** @type {HTMLInputElement} */ (event.target);
|
||||||
Array.from(tocList.querySelectorAll("li")).forEach((item) => {
|
const query = target.value.trim().toLowerCase();
|
||||||
|
Array.from(tocList?.querySelectorAll("li") ?? []).forEach((item) => {
|
||||||
const text = item.textContent.toLowerCase();
|
const text = item.textContent.toLowerCase();
|
||||||
item.style.display = text.includes(query) ? "" : "none";
|
item.style.display = text.includes(query) ? "" : "none";
|
||||||
});
|
});
|
||||||
|
|
@ -252,10 +258,9 @@ const linkifyTextNode = (node) => {
|
||||||
node.parentNode.replaceChild(fragment, node);
|
node.parentNode.replaceChild(fragment, node);
|
||||||
};
|
};
|
||||||
|
|
||||||
const walker = document.createTreeWalker(
|
const contentEl = document.getElementById("content");
|
||||||
document.getElementById("content"),
|
if (contentEl) {
|
||||||
NodeFilter.SHOW_TEXT,
|
const walker = document.createTreeWalker(contentEl, NodeFilter.SHOW_TEXT, {
|
||||||
{
|
|
||||||
acceptNode: (node) => {
|
acceptNode: (node) => {
|
||||||
const parent = node.parentElement;
|
const parent = node.parentElement;
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
|
|
@ -269,16 +274,16 @@ const walker = document.createTreeWalker(
|
||||||
}
|
}
|
||||||
return NodeFilter.FILTER_ACCEPT;
|
return NodeFilter.FILTER_ACCEPT;
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const nodesToLinkify = [];
|
const nodesToLinkify = [];
|
||||||
while (walker.nextNode()) {
|
while (walker.nextNode()) {
|
||||||
nodesToLinkify.push(walker.currentNode);
|
nodesToLinkify.push(walker.currentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodesToLinkify.forEach((node) => linkifyTextNode(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
nodesToLinkify.forEach((node) => linkifyTextNode(node));
|
|
||||||
|
|
||||||
// TOC collapse/expand toggle
|
// TOC collapse/expand toggle
|
||||||
const tocToggle = document.createElement("button");
|
const tocToggle = document.createElement("button");
|
||||||
tocToggle.className = "toc-toggle";
|
tocToggle.className = "toc-toggle";
|
||||||
|
|
@ -290,7 +295,7 @@ const toc = document.querySelector(".toc");
|
||||||
const layout = document.querySelector(".layout");
|
const layout = document.querySelector(".layout");
|
||||||
|
|
||||||
tocToggle.addEventListener("click", () => {
|
tocToggle.addEventListener("click", () => {
|
||||||
toc.classList.toggle("collapsed");
|
toc?.classList.toggle("collapsed");
|
||||||
layout.classList.toggle("toc-hidden");
|
layout?.classList.toggle("toc-hidden");
|
||||||
document.body.classList.toggle("toc-collapsed");
|
document.body.classList.toggle("toc-collapsed");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue