Tighten novella reader page chrome
This commit is contained in:
parent
17a1eb9ddc
commit
cfdbaea6f3
6 changed files with 25 additions and 79 deletions
Binary file not shown.
|
|
@ -28,27 +28,6 @@ class Chapter:
|
|||
blocks: list[str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class SpinePage:
|
||||
chapter_index: int
|
||||
page_index: int
|
||||
title: str
|
||||
movement: str
|
||||
blocks: list[str]
|
||||
|
||||
@property
|
||||
def href(self) -> str:
|
||||
if self.page_index == 1:
|
||||
return f"text/chapter-{self.chapter_index:02}.xhtml"
|
||||
return f"text/chapter-{self.chapter_index:02}-p{self.page_index:02}.xhtml"
|
||||
|
||||
@property
|
||||
def item_id(self) -> str:
|
||||
if self.page_index == 1:
|
||||
return f"chapter-{self.chapter_index}"
|
||||
return f"chapter-{self.chapter_index}-p{self.page_index}"
|
||||
|
||||
|
||||
def inline_markup(text: str) -> str:
|
||||
placeholders: dict[str, str] = {}
|
||||
|
||||
|
|
@ -113,40 +92,6 @@ def chapter_xhtml(chapter: Chapter) -> str:
|
|||
return xhtml_page(chapter.title, body, "chapter")
|
||||
|
||||
|
||||
def paginate_chapters(chapters: list[Chapter], target_chars: int = 1400) -> list[SpinePage]:
|
||||
pages: list[SpinePage] = []
|
||||
for chapter_index, chapter in enumerate(chapters, 1):
|
||||
chunks: list[list[str]] = []
|
||||
current: list[str] = []
|
||||
current_chars = 0
|
||||
for block in chapter.blocks:
|
||||
block_chars = len(block)
|
||||
if current and current_chars + block_chars > target_chars:
|
||||
chunks.append(current)
|
||||
current = []
|
||||
current_chars = 0
|
||||
current.append(block)
|
||||
current_chars += block_chars
|
||||
if current:
|
||||
chunks.append(current)
|
||||
for page_index, blocks in enumerate(chunks, 1):
|
||||
pages.append(SpinePage(chapter_index, page_index, chapter.title, chapter.movement, blocks))
|
||||
return pages
|
||||
|
||||
|
||||
def page_xhtml(page: SpinePage) -> str:
|
||||
paragraphs = []
|
||||
for block in page.blocks:
|
||||
rendered = inline_markup(block)
|
||||
css_class = " class=\"pov\"" if re.fullmatch(r"<strong>[^<]+</strong>", rendered) else ""
|
||||
paragraphs.append(f"<p{css_class}>{rendered}</p>")
|
||||
header = ""
|
||||
if page.page_index == 1:
|
||||
header = f'<header><p class="movement">{html.escape(page.movement)}</p><h1>{html.escape(page.title)}</h1></header>'
|
||||
body = header + "\n".join(paragraphs)
|
||||
return xhtml_page(page.title, body, "chapter")
|
||||
|
||||
|
||||
def modified_timestamp(source: Path) -> str:
|
||||
epoch = int(os.environ.get("SOURCE_DATE_EPOCH", source.stat().st_mtime))
|
||||
return datetime.fromtimestamp(epoch, timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
|
@ -154,13 +99,12 @@ def modified_timestamp(source: Path) -> str:
|
|||
|
||||
def build(source: Path, output: Path) -> None:
|
||||
title, chapters = parse_manuscript(source)
|
||||
pages = paginate_chapters(chapters)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
chapter_items = "\n".join(
|
||||
f'<item id="{page.item_id}" href="{page.href}" media-type="application/xhtml+xml"/>'
|
||||
for page in pages
|
||||
f'<item id="chapter-{i}" href="text/chapter-{i:02}.xhtml" media-type="application/xhtml+xml"/>'
|
||||
for i in range(1, len(chapters) + 1)
|
||||
)
|
||||
spine = "\n".join(f'<itemref idref="{page.item_id}"/>' for page in pages)
|
||||
spine = "\n".join(f'<itemref idref="chapter-{i}"/>' for i in range(1, len(chapters) + 1))
|
||||
nav_points = "\n".join(
|
||||
f'<li><a href="text/chapter-{i:02}.xhtml">{html.escape(chapter.title)}</a></li>'
|
||||
for i, chapter in enumerate(chapters, 1)
|
||||
|
|
@ -191,8 +135,8 @@ def build(source: Path, output: Path) -> None:
|
|||
archive.writestr("EPUB/toc.ncx", ncx)
|
||||
archive.writestr("EPUB/styles/book.css", css)
|
||||
archive.writestr("EPUB/text/title.xhtml", title_page)
|
||||
for page in pages:
|
||||
archive.writestr(f"EPUB/{page.href}", page_xhtml(page))
|
||||
for i, chapter in enumerate(chapters, 1):
|
||||
archive.writestr(f"EPUB/text/chapter-{i:02}.xhtml", chapter_xhtml(chapter))
|
||||
validate(output, len(chapters))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,19 +25,28 @@ export const defaultContentPageLayout: PageLayout = {
|
|||
showCurrentPage: false,
|
||||
showRoot: false,
|
||||
}),
|
||||
condition: (page) => page.fileData.slug !== "index",
|
||||
condition: (page) => page.fileData.slug !== "index" && page.fileData.slug !== "Fiction/The-Burden-of-Proof",
|
||||
}),
|
||||
Component.ConditionalRender({
|
||||
component: Component.ArticleTitle(),
|
||||
condition: (page) => !page.fileData.slug?.endsWith("/index") && page.fileData.slug !== "index",
|
||||
condition: (page) =>
|
||||
!page.fileData.slug?.endsWith("/index") &&
|
||||
page.fileData.slug !== "index" &&
|
||||
page.fileData.slug !== "Fiction/The-Burden-of-Proof",
|
||||
}),
|
||||
Component.ConditionalRender({
|
||||
component: AetheriaAuthorMeta(),
|
||||
condition: (page) => !page.fileData.slug?.endsWith("/index") && page.fileData.slug !== "index",
|
||||
condition: (page) =>
|
||||
!page.fileData.slug?.endsWith("/index") &&
|
||||
page.fileData.slug !== "index" &&
|
||||
page.fileData.slug !== "Fiction/The-Burden-of-Proof",
|
||||
}),
|
||||
Component.ConditionalRender({
|
||||
component: Component.ContentMeta(),
|
||||
condition: (page) => !page.fileData.slug?.endsWith("/index") && page.fileData.slug !== "index",
|
||||
condition: (page) =>
|
||||
!page.fileData.slug?.endsWith("/index") &&
|
||||
page.fileData.slug !== "index" &&
|
||||
page.fileData.slug !== "Fiction/The-Burden-of-Proof",
|
||||
}),
|
||||
Component.ConditionalRender({
|
||||
component: AetheriaEpubReader(),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default (() => {
|
|||
const AetheriaEpubReader: QuartzComponent = () => (
|
||||
<section
|
||||
class="aetheria-epub-reader"
|
||||
data-epub-src="/static/fiction/the-burden-of-proof.epub?v=reader-pages-2"
|
||||
data-epub-src="/static/fiction/the-burden-of-proof.epub?v=reader-native-pages-1"
|
||||
aria-label="The Burden of Proof ebook reader"
|
||||
>
|
||||
<div class="aetheria-reader-heading">
|
||||
|
|
@ -32,7 +32,7 @@ export default (() => {
|
|||
</label>
|
||||
<label>
|
||||
<span>Spread</span>
|
||||
<select data-reader-setting="spread"><option value="none">Single</option><option value="auto">Auto</option></select>
|
||||
<select data-reader-setting="spread"><option value="none">Single</option><option value="always">Two Pages</option></select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Theme</span>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ type ReaderSettings = {
|
|||
fontSize: number
|
||||
lineHeight: number
|
||||
flow: "paginated" | "scrolled-doc"
|
||||
spread: "auto" | "none"
|
||||
spread: "always" | "none"
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
|
@ -17,7 +17,7 @@ declare global {
|
|||
}
|
||||
|
||||
const cookieName = "aetheria_tbp_reader"
|
||||
const settingsVersion = 3
|
||||
const settingsVersion = 4
|
||||
const defaults: ReaderSettings = {
|
||||
version: settingsVersion,
|
||||
theme: "dark",
|
||||
|
|
@ -41,7 +41,7 @@ function readSettings(): ReaderSettings {
|
|||
fontSize: Number.isFinite(saved.fontSize) ? Math.min(150, Math.max(80, saved.fontSize)) : defaults.fontSize,
|
||||
lineHeight: Number.isFinite(saved.lineHeight) ? Math.min(210, Math.max(130, saved.lineHeight)) : defaults.lineHeight,
|
||||
flow: ["paginated", "scrolled-doc"].includes(saved.flow) ? saved.flow : defaults.flow,
|
||||
spread: saved.version === settingsVersion && ["auto", "none"].includes(saved.spread) ? saved.spread : defaults.spread,
|
||||
spread: saved.version === settingsVersion && ["always", "none"].includes(saved.spread) ? saved.spread : defaults.spread,
|
||||
}
|
||||
} catch {
|
||||
return { ...defaults }
|
||||
|
|
@ -112,12 +112,6 @@ async function initReader(root: HTMLElement) {
|
|||
viewport.style.width = "100%"
|
||||
viewport.style.maxWidth = "100%"
|
||||
viewport.style.overflow = "hidden"
|
||||
viewport.querySelectorAll<HTMLElement>(".epub-container, .epub-view, iframe").forEach((element) => {
|
||||
element.style.setProperty("width", "100%", "important")
|
||||
element.style.setProperty("max-width", "100%", "important")
|
||||
element.style.setProperty("min-width", "0", "important")
|
||||
if (element.classList.contains("epub-view")) element.style.setProperty("flex", "0 0 100%", "important")
|
||||
})
|
||||
}
|
||||
|
||||
async function displayLocation(target?: string) {
|
||||
|
|
|
|||
|
|
@ -114,10 +114,9 @@ body[data-slug="Fiction/The-Burden-of-Proof"] {
|
|||
contain: layout paint;
|
||||
background: #11161b;
|
||||
|
||||
iframe,
|
||||
.epub-container {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
position: absolute !important;
|
||||
inset: 0 auto auto 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue