Files

159 lines
7.6 KiB
HTML
Raw Permalink Normal View History

<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title }} — PI Weekly Reader</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📰</text></svg>">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<style>
body { margin: 0; overflow: hidden; display: flex; flex-direction: column; height: 100vh; }
.reader-header {
display: flex; align-items: center; gap: 1rem;
padding: 0.5rem 1rem; border-bottom: 1px solid var(--pico-muted-border-color);
flex-shrink: 0;
}
.reader-header h2 { margin: 0; font-size: 1rem; flex: 1; }
.reader-body { display: flex; flex: 1; overflow: hidden; }
.toc-sidebar {
width: 260px; overflow-y: auto; border-right: 1px solid var(--pico-muted-border-color);
padding: 1rem; flex-shrink: 0; background: var(--pico-background-color);
}
.toc-sidebar.collapsed { display: none; }
.toc-sidebar h3 { font-size: 0.75rem; text-transform: uppercase; color: var(--pico-muted-color); margin-bottom: 0.5rem; }
.toc-item {
display: block; padding: 0.4rem 0.5rem; border-radius: var(--pico-border-radius);
font-size: 0.85rem; cursor: pointer; text-decoration: none; color: inherit;
}
.toc-item:hover { background: var(--pico-secondary-hover-background); }
.toc-item.active { background: var(--pico-primary-background); color: var(--pico-primary-inverse); }
#reader-viewport { flex: 1; overflow: hidden; }
.reader-footer {
display: flex; justify-content: space-between; align-items: center;
padding: 0.4rem 1rem; border-top: 1px solid var(--pico-muted-border-color);
flex-shrink: 0;
}
.reader-footer small { color: var(--pico-muted-color); }
</style>
</head>
<body>
<div class="reader-header">
<a href="/issues" class="outline" role="button" style="padding:0.3rem 0.8rem;">&larr; Issues</a>
<h2>{{ title }}</h2>
<button class="outline" onclick="toggleToc()" style="padding:0.3rem 0.8rem;">TOC</button>
</div>
<div class="reader-body">
<div class="toc-sidebar" id="toc-sidebar">
<h3>Table of Contents</h3>
<div id="toc-list"></div>
</div>
<div id="reader-viewport"></div>
</div>
<div class="reader-footer">
<button class="outline" id="prev-btn" onclick="rendition && rendition.prev()" style="padding:0.3rem 1rem;">&larr; Previous</button>
<small id="chapter-info"></small>
<button class="outline" id="next-btn" onclick="rendition && rendition.next()" style="padding:0.3rem 1rem;">Next &rarr;</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/epubjs/dist/epub.min.js"></script>
<script>
let book, rendition;
fetch("/issues/{{ issue.id }}/epub")
.then(r => r.arrayBuffer())
.then(buf => {
book = ePub(buf);
rendition = book.renderTo("reader-viewport", {
width: "100%",
height: "100%",
spread: "none",
});
rendition.themes.default({
body: {
"font-family": 'system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif',
"line-height": "1.6",
},
"h1, h2, h3": {
"font-family": 'system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif',
},
});
rendition.display();
book.loaded.navigation.then(function(nav) {
const tocList = document.getElementById("toc-list");
const coverItem = document.createElement("a");
coverItem.className = "toc-item";
coverItem.textContent = "Cover";
coverItem.href = "#";
coverItem.onclick = function(e) {
e.preventDefault();
rendition.display("cover.xhtml");
document.querySelectorAll(".toc-item").forEach(i => i.classList.remove("active"));
coverItem.classList.add("active");
};
tocList.appendChild(coverItem);
nav.toc.forEach(function(chapter) {
const item = document.createElement("a");
item.className = "toc-item";
item.textContent = chapter.label.trim();
item.href = "#";
item.onclick = function(e) {
e.preventDefault();
rendition.display(chapter.href);
document.querySelectorAll(".toc-item").forEach(i => i.classList.remove("active"));
item.classList.add("active");
};
tocList.appendChild(item);
});
});
rendition.on("relocated", function(location) {
const href = location.start.href;
document.querySelectorAll(".toc-item").forEach(function(item, idx) {
if (idx === 0) {
item.classList.toggle("active", href.includes("cover.xhtml"));
}
});
if (book.navigation && book.navigation.toc) {
const current = book.navigation.toc.findIndex(
ch => href.includes(ch.href.split('#')[0])
);
const total = book.navigation.toc.length;
if (current >= 0) {
document.getElementById("chapter-info").textContent =
`${current + 1} of ${total}`;
document.querySelectorAll(".toc-item").forEach((item, idx) => {
if (idx > 0) item.classList.toggle("active", idx - 1 === current);
else item.classList.remove("active");
});
} else if (href.includes("cover.xhtml")) {
document.getElementById("chapter-info").textContent = "Cover";
} else if (href.includes("nav.xhtml")) {
document.getElementById("chapter-info").textContent = "Table of Contents";
}
}
});
document.addEventListener("keydown", function(e) {
if (e.key === "ArrowLeft" && rendition) rendition.prev();
if (e.key === "ArrowRight" && rendition) rendition.next();
});
})
.catch(err => {
document.getElementById("reader-viewport").innerHTML =
`<p style="padding:2rem;color:red;">Failed to load ePub: ${err.message}</p>`;
});
function toggleToc() {
document.getElementById("toc-sidebar").classList.toggle("collapsed");
}
</script>
</body>
</html>