About, Listen & lyrics (real player, synced lyrics, Watch), Paths (strip when the timeline is hidden, motif rows, nine-place chart), Fan works. Co-Authored-By: Claude Opus 5.5 <[email protected]>
46 lines
2.7 KiB
TypeScript
46 lines
2.7 KiB
TypeScript
// The reading view: the song's book of the Odyssey (Butcher & Lang, which keeps the Greek names), opened at
|
|
// the first paragraph naming one of the song's keywords, with the paragraphs before it folded away.
|
|
import type { AlbumSong } from "../../data/album";
|
|
import { keywordRe, loadBook, passageStart, roman, stripFootnotes } from "../content";
|
|
import { esc } from "./util";
|
|
|
|
/** `readAll` shows the whole book from the start, keeping the passage marker. */
|
|
export function readingView(song: AlbumSong, readAll: boolean): HTMLElement {
|
|
const o = song.odyssey!;
|
|
const el = document.createElement("div");
|
|
el.className = "reading";
|
|
el.innerHTML = `
|
|
<button type="button" class="reading__back" data-act="unread"><span aria-hidden="true">←</span> Back to About</button>
|
|
<p class="reading__eyebrow">The Odyssey · Book ${roman(o.book)} · tr. Butcher & Lang</p>
|
|
<div class="reading__body" aria-busy="true"><p class="hint">Opening Book ${roman(o.book)}…</p></div>`;
|
|
const body = el.querySelector<HTMLElement>(".reading__body")!;
|
|
loadBook(o.book).then((book) => {
|
|
if (!el.isConnected) return; // the reader has moved on
|
|
body.removeAttribute("aria-busy");
|
|
if (!book) {
|
|
body.innerHTML = `<p class="note">Book ${roman(o.book)} could not be loaded from the content folder.
|
|
<a href="${esc(o.url)}" target="_blank" rel="noopener noreferrer">Read it on Project Gutenberg</a>.</p>`;
|
|
return;
|
|
}
|
|
const at = passageStart(book, o.keywords);
|
|
const re = keywordRe(o.keywords);
|
|
const para = (t: string) => {
|
|
let h = esc(stripFootnotes(t));
|
|
if (re) h = h.replace(re, "<mark>$1</mark>");
|
|
return `<p class="reading__p">${h}</p>`;
|
|
};
|
|
const from = readAll ? 0 : at.index;
|
|
const marker = `<p class="reading__mark" id="reading-mark">${at.word
|
|
? `The passage for ${esc(song.title)} begins here · first mention of “${esc(at.word)}”`
|
|
: `No keyword for ${esc(song.title)} found · Book ${roman(o.book)} from the start`}</p>`;
|
|
body.innerHTML = `
|
|
<h3 class="reading__title">${esc(book.heading.replace(/^BOOK\b/, "Book").replace(/\.$/, ""))}</h3>
|
|
${book.summary ? `<p class="reading__sum">${esc(book.summary)}</p>` : ""}
|
|
${from > 0 ? `<button type="button" class="reading__more" data-act="read-all">Show the ${from} paragraph${from === 1 ? "" : "s"} before this passage</button>` : ""}
|
|
${book.paragraphs.slice(from).map((t, i) => (from + i === at.index ? marker : "") + para(t)).join("")}
|
|
<p class="reading__end">End of Book ${roman(o.book)} · Project Gutenberg #1728</p>`;
|
|
if (readAll) el.querySelector<HTMLElement>("#reading-mark")?.scrollIntoView?.({ block: "start" });
|
|
});
|
|
return el;
|
|
}
|