Files
foliate-js/reader.html
T
John Factotum 1e952102bd PDF: rerender when scale changes
The fixed-layout renderer now accepts an object for the return value of
`load()` (on the `book.sections` items). If it's an object, the `src`
is the URL of the section, and `onZoom`, if present, will be called
when the viewport is zoomed. With this you can scale the page yourself
rather than relying on the renderer's own scaling with CSS transform.

Also upgraded to PDF.js 4.6.82. And now internal links are broken...
Don't know how to fix that yet.
2024-09-28 12:12:59 +08:00

305 lines
7.5 KiB
HTML

<!DOCTYPE html>
<meta charset="utf-8">
<meta name="color-scheme" content="light dark">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' blob:; script-src 'self'; style-src 'self' blob: 'unsafe-inline'; img-src 'self' blob: data:; connect-src 'self' blob: data: *; frame-src blob: data:; object-src blob: data:; form-action 'none';">
<title>E-Book Reader</title>
<style>
:root {
--active-bg: rgba(0, 0, 0, .05);
}
@supports (color-scheme: light dark) {
@media (prefers-color-scheme: dark) {
:root {
--active-bg: rgba(255, 255, 255, .1);
}
}
}
html {
height: 100%;
}
body {
margin: 0 auto;
height: 100%;
font: menu;
font-family: system-ui, sans-serif;
}
#drop-target {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
visibility: hidden;
}
#drop-target h1 {
font-weight: 900;
}
#file-button {
font: inherit;
background: none;
border: 0;
padding: 0;
text-decoration: underline;
cursor: pointer;
}
.icon {
display: block;
fill: none;
stroke: currentcolor;
stroke-width: 2px;
}
.empty-state-icon {
margin: auto;
}
.toolbar {
box-sizing: border-box;
position: absolute;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 48px;
padding: 6px;
transition: opacity 250ms ease;
visibility: hidden;
}
.toolbar button {
padding: 3px;
border-radius: 6px;
background: none;
border: 0;
color: GrayText;
}
.toolbar button:hover {
background: rgba(0, 0, 0, .1);
color: currentcolor;
}
#header-bar {
top: 0;
}
#nav-bar {
bottom: 0;
}
#progress-slider {
flex-grow: 1;
margin: 0 12px;
visibility: hidden;
}
#side-bar {
visibility: hidden;
box-sizing: border-box;
position: absolute;
z-index: 2;
top: 0;
left: 0;
height: 100%;
width: 320px;
transform: translateX(-320px);
display: flex;
flex-direction: column;
background: Canvas;
color: CanvasText;
box-shadow: 0 0 0 1px rgba(0, 0, 0, .2), 0 0 40px rgba(0, 0, 0, .2);
transition: visibility 0s linear 300ms, transform 300ms ease;
}
#side-bar.show {
visibility: visible;
transform: translateX(0);
transition-delay: 0s;
}
#dimming-overlay {
visibility: hidden;
position: fixed;
z-index: 2;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, .2);
opacity: 0;
transition: visibility 0s linear 300ms, opacity 300ms ease;
}
#dimming-overlay.show {
visibility: visible;
opacity: 1;
transition-delay: 0s;
}
#side-bar-header {
padding: 1rem;
display: flex;
border-bottom: 1px solid rgba(0, 0, 0, .1);
align-items: center;
}
#side-bar-cover {
height: 10vh;
min-height: 60px;
max-height: 180px;
border-radius: 3px;
border: 0;
background: lightgray;
box-shadow: 0 0 1px rgba(0, 0, 0, .1), 0 0 16px rgba(0, 0, 0, .1);
margin-inline-end: 1rem;
}
#side-bar-cover:not([src]) {
display: none;
}
#side-bar-title {
margin: .5rem 0;
font-size: inherit;
}
#side-bar-author {
margin: .5rem 0;
font-size: small;
color: GrayText;
}
#toc-view {
padding: .5rem;
overflow-y: scroll;
}
#toc-view li, #toc-view ol {
margin: 0;
padding: 0;
list-style: none;
}
#toc-view a, #toc-view span {
display: block;
border-radius: 6px;
padding: 8px;
margin: 2px 0;
}
#toc-view a {
color: CanvasText;
text-decoration: none;
}
#toc-view a:hover {
background: var(--active-bg);
}
#toc-view span {
color: GrayText;
}
#toc-view svg {
margin-inline-start: -24px;
padding-inline-start: 5px;
padding-inline-end: 6px;
fill: CanvasText;
cursor: default;
transition: transform .2s ease;
opacity: .5;
}
#toc-view svg:hover {
opacity: 1;
}
#toc-view [aria-current] {
font-weight: bold;
background: var(--active-bg);
}
#toc-view [aria-expanded="false"] svg {
transform: rotate(-90deg);
}
#toc-view [aria-expanded="false"] + [role="group"] {
display: none;
}
.menu-container {
position: relative;
}
.menu, .menu ul {
list-style: none;
padding: 0;
margin: 0;
}
.menu {
visibility: hidden;
position: absolute;
right: 0;
background: Canvas;
color: CanvasText;
border-radius: 6px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, .2), 0 0 16px rgba(0, 0, 0, .1);
padding: 6px;
cursor: default;
}
.menu.show {
visibility: visible;
}
.menu li {
padding: 6px 12px;
padding-left: 24px;
border-radius: 6px;
}
.menu li:hover {
background: var(--active-bg);
}
.menu li[aria-checked="true"] {
background-position: center left;
background-repeat: no-repeat;
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%223%22%2F%3E%3C%2Fsvg%3E');
}
.popover {
background: Canvas;
color: CanvasText;
border-radius: 6px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, .2), 0 0 16px rgba(0, 0, 0, .1), 0 0 32px rgba(0, 0, 0, .1);
}
.popover-arrow-down {
fill: Canvas;
filter: drop-shadow(0 -1px 0 rgba(0, 0, 0, .2));
}
.popover-arrow-up {
fill: Canvas;
filter: drop-shadow(0 1px 0 rgba(0, 0, 0, .2));
}
</style>
<input type="file" id="file-input" hidden>
<div id="drop-target" class="filter">
<div>
<svg class="icon empty-state-icon" width="72" height="72" aria-hidden="true">
<path d="M36 18s-6-6-12-6-15 6-15 6v42s9-6 15-6 12 6 12 6c4-4 8-6 12-6s12 2 15 6V18c-6-4-12-6-15-6-4 0-8 2-12 6m0 0v42"/>
</svg>
<h1>Drop a book here!</h1>
<p>Or <button id="file-button">choose a file</button> to open it.</p>
</div>
</div>
<div id="dimming-overlay" aria-hidden="true"></div>
<div id="side-bar">
<div id="side-bar-header">
<img id="side-bar-cover">
<div>
<h1 id="side-bar-title"></h1>
<p id="side-bar-author"></p>
</div>
</div>
<div id="toc-view"></div>
</div>
<div id="header-bar" class="toolbar">
<button id="side-bar-button" aria-label="Show sidebar">
<svg class="icon" width="24" height="24" aria-hidden="true">
<path d="M 4 6 h 16 M 4 12 h 16 M 4 18 h 16"/>
</svg>
</button>
<div id="menu-button" class="menu-container">
<button aria-label="Show settings" aria-haspopup="true">
<svg class="icon" width="24" height="24" aria-hidden="true">
<path d="M5 12.7a7 7 0 0 1 0-1.4l-1.8-2 2-3.5 2.7.5a7 7 0 0 1 1.2-.7L10 3h4l.9 2.6 1.2.7 2.7-.5 2 3.4-1.8 2a7 7 0 0 1 0 1.5l1.8 2-2 3.5-2.7-.5a7 7 0 0 1-1.2.7L14 21h-4l-.9-2.6a7 7 0 0 1-1.2-.7l-2.7.5-2-3.4 1.8-2Z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
</button>
</div>
</div>
<div id="nav-bar" class="toolbar">
<button id="left-button" aria-label="Go left">
<svg class="icon" width="24" height="24" aria-hidden="true">
<path d="M 15 6 L 9 12 L 15 18"/>
</svg>
</button>
<input id="progress-slider" type="range" min="0" max="1" step="any" list="tick-marks">
<datalist id="tick-marks"></datalist>
<button id="right-button" aria-label="Go right">
<svg class="icon" width="24" height="24" aria-hidden="true">
<path d="M 9 6 L 15 12 L 9 18"/>
</svg>
</button>
</div>
<script src="reader.js" type="module"></script>