Fix Reader zoom and result layout
Browse files- static/reader.css +6 -5
- static/reader.js +6 -1
- static/style.css +23 -0
static/reader.css
CHANGED
|
@@ -23,19 +23,20 @@ body { margin: 0; height: 100vh; height: 100dvh; overflow: hidden; display: flex
|
|
| 23 |
.reader-viewport { min-height: 0; flex: 1; overflow: auto; background: #121416; }
|
| 24 |
.loading-status { display: none; color: #aeb5ba; font-size: 13px; text-align: center; padding: 12px; }
|
| 25 |
.loading-status[hidden] { display: none; }
|
| 26 |
-
.reader-content { --reader-zoom: 1; width: min(
|
| 27 |
.reader-page { position: relative; margin: 0 auto 18px; background: white; box-shadow: 0 2px 14px #0008; min-height: 160px; }
|
|
|
|
| 28 |
.reader-page canvas { display: block; width: 100%; height: auto; }
|
| 29 |
.reader-page canvas { position: relative; opacity: 0; transition: opacity 120ms ease; }
|
| 30 |
.reader-page canvas.ready { opacity: 1; }
|
| 31 |
-
.reader-image { display: block;
|
| 32 |
-
.reader-text { margin: 0 auto; max-width: 76ch; white-space: pre-wrap; overflow-wrap: anywhere; font: 17px/1.8 ui-monospace, monospace; color: #e7e9eb; }
|
| 33 |
-
.reader-markdown { margin: 0 auto; max-width: 76ch; font-size: 17px; line-height: 1.75; }
|
| 34 |
.reader-markdown img { max-width: 100%; }
|
| 35 |
.reader-markdown a { color: #72a7df; }
|
| 36 |
.reader-error { margin: 20vh auto; max-width: 560px; color: #d7dadd; text-align: center; line-height: 1.6; }
|
| 37 |
.epub-frame { width: 100%; height: calc(100vh - 96px); background: white; }
|
| 38 |
.docx-body { overflow-x: auto; color: #111; }
|
| 39 |
-
.docx-body .reader-docx-wrapper { background: transparent; padding: 0; }
|
| 40 |
.docx-body .reader-docx-wrapper > section.reader-docx { margin: 0 auto 18px; box-shadow: 0 2px 14px #0008; }
|
| 41 |
@media (max-width: 600px) { .reader-toolbar { min-height: 36px; gap: 1px; padding: 2px 5px; } .reader-content { padding: 10px; } .reader-heading { display: none; } .reader-actions { flex: 1; justify-content: flex-end; gap: 1px; min-width: 0; } .reader-actions .control-group { gap: 0; } .reader-actions .icon-button { width: 26px; } .reader-actions .text-button { height: 30px; padding: 0 3px; } .loading-status { display: block; padding: 10px 6px 0; } .icon-button { width: 26px; height: 30px; flex: none; } .compact-input { height: 30px; padding-right: 3px; font-size: 11px; } .compact-input input { width: 24px; height: 28px; } .zoom-controls .compact-input input { width: 32px; } .history-panel { top: 36px; } }
|
|
|
|
| 23 |
.reader-viewport { min-height: 0; flex: 1; overflow: auto; background: #121416; }
|
| 24 |
.loading-status { display: none; color: #aeb5ba; font-size: 13px; text-align: center; padding: 12px; }
|
| 25 |
.loading-status[hidden] { display: none; }
|
| 26 |
+
.reader-content { --reader-zoom: 1; width: min(100%, 1100px); margin: 0 auto; padding: 20px; }
|
| 27 |
.reader-page { position: relative; margin: 0 auto 18px; background: white; box-shadow: 0 2px 14px #0008; min-height: 160px; }
|
| 28 |
+
.reader-content[data-mode="pdf"] .reader-page { width: calc(100% * var(--reader-zoom)); }
|
| 29 |
.reader-page canvas { display: block; width: 100%; height: auto; }
|
| 30 |
.reader-page canvas { position: relative; opacity: 0; transition: opacity 120ms ease; }
|
| 31 |
.reader-page canvas.ready { opacity: 1; }
|
| 32 |
+
.reader-image { display: block; width: calc(100% * var(--reader-zoom)); max-width: none; height: auto; margin: 0 auto; background: #fff; }
|
| 33 |
+
.reader-text { margin: 0 auto; max-width: 76ch; white-space: pre-wrap; overflow-wrap: anywhere; font: calc(17px * var(--reader-zoom))/1.8 ui-monospace, monospace; color: #e7e9eb; }
|
| 34 |
+
.reader-markdown { margin: 0 auto; max-width: 76ch; font-size: calc(17px * var(--reader-zoom)); line-height: 1.75; }
|
| 35 |
.reader-markdown img { max-width: 100%; }
|
| 36 |
.reader-markdown a { color: #72a7df; }
|
| 37 |
.reader-error { margin: 20vh auto; max-width: 560px; color: #d7dadd; text-align: center; line-height: 1.6; }
|
| 38 |
.epub-frame { width: 100%; height: calc(100vh - 96px); background: white; }
|
| 39 |
.docx-body { overflow-x: auto; color: #111; }
|
| 40 |
+
.docx-body .reader-docx-wrapper { background: transparent; padding: 0; zoom: var(--reader-zoom); }
|
| 41 |
.docx-body .reader-docx-wrapper > section.reader-docx { margin: 0 auto 18px; box-shadow: 0 2px 14px #0008; }
|
| 42 |
@media (max-width: 600px) { .reader-toolbar { min-height: 36px; gap: 1px; padding: 2px 5px; } .reader-content { padding: 10px; } .reader-heading { display: none; } .reader-actions { flex: 1; justify-content: flex-end; gap: 1px; min-width: 0; } .reader-actions .control-group { gap: 0; } .reader-actions .icon-button { width: 26px; } .reader-actions .text-button { height: 30px; padding: 0 3px; } .loading-status { display: block; padding: 10px 6px 0; } .icon-button { width: 26px; height: 30px; flex: none; } .compact-input { height: 30px; padding-right: 3px; font-size: 11px; } .compact-input input { width: 24px; height: 28px; } .zoom-controls .compact-input input { width: 32px; } .history-panel { top: 36px; } }
|
static/reader.js
CHANGED
|
@@ -35,6 +35,7 @@ const downloadUrl = params.get("download") || sourceUrl;
|
|
| 35 |
const extension = (params.get("ext") || "").toLowerCase();
|
| 36 |
const capability = VoiceOfMLReader.capability(extension);
|
| 37 |
const content = document.querySelector("#content");
|
|
|
|
| 38 |
const status = document.querySelector("#status");
|
| 39 |
const loadingStatus = document.querySelector("#loading-status");
|
| 40 |
const title = params.get("title") || "在线阅读";
|
|
@@ -87,10 +88,13 @@ document.querySelector("#back").addEventListener("click", () => {
|
|
| 87 |
});
|
| 88 |
function setZoom(percent, persist = true) {
|
| 89 |
const normalized = VoiceOfMLReader.clampNumber(percent, 50, 250, 100);
|
|
|
|
| 90 |
zoom = normalized / 100;
|
| 91 |
content.style.setProperty("--reader-zoom", String(zoom));
|
| 92 |
zoomInput.value = String(normalized);
|
|
|
|
| 93 |
if (pdfDocument) rerenderVisiblePdfPages();
|
|
|
|
| 94 |
if (persist) scheduleSave();
|
| 95 |
}
|
| 96 |
for (const [id, delta] of [["#zoom-out", -10], ["#zoom-in", 10]]) {
|
|
@@ -222,7 +226,7 @@ async function renderPdfShell(shell, force = false, priority = false) {
|
|
| 222 |
try {
|
| 223 |
const page = await pdfDocument.getPage(Number(shell.dataset.page));
|
| 224 |
const base = page.getViewport({ scale: 1 });
|
| 225 |
-
const scale = Math.min(3, Math.max(0.5,
|
| 226 |
const rendered = page.getViewport({ scale });
|
| 227 |
const canvas = shell.querySelector("canvas");
|
| 228 |
canvas.width = rendered.width; canvas.height = rendered.height;
|
|
@@ -377,6 +381,7 @@ async function renderEpub(prepared) {
|
|
| 377 |
}
|
| 378 |
async function displayEpub(url, frame) {
|
| 379 |
const book = ePub(url); epubRendition = book.renderTo(frame, { width: "100%", height: "100%", spread: "none", flow: "scrolled-doc" });
|
|
|
|
| 380 |
epubRendition.on("relocated", (location) => { epubLocation = location && location.start ? location.start.cfi : ""; scheduleSave(); });
|
| 381 |
const restoredLocation = restoredEntry && restoredEntry.epubLocation;
|
| 382 |
try { await epubRendition.display(restoredLocation || undefined); }
|
|
|
|
| 35 |
const extension = (params.get("ext") || "").toLowerCase();
|
| 36 |
const capability = VoiceOfMLReader.capability(extension);
|
| 37 |
const content = document.querySelector("#content");
|
| 38 |
+
content.dataset.mode = capability.mode || "unsupported";
|
| 39 |
const status = document.querySelector("#status");
|
| 40 |
const loadingStatus = document.querySelector("#loading-status");
|
| 41 |
const title = params.get("title") || "在线阅读";
|
|
|
|
| 88 |
});
|
| 89 |
function setZoom(percent, persist = true) {
|
| 90 |
const normalized = VoiceOfMLReader.clampNumber(percent, 50, 250, 100);
|
| 91 |
+
const horizontalCenter = viewport.scrollWidth ? (viewport.scrollLeft + viewport.clientWidth / 2) / viewport.scrollWidth : 0;
|
| 92 |
zoom = normalized / 100;
|
| 93 |
content.style.setProperty("--reader-zoom", String(zoom));
|
| 94 |
zoomInput.value = String(normalized);
|
| 95 |
+
if (epubRendition) epubRendition.themes.fontSize(`${normalized}%`);
|
| 96 |
if (pdfDocument) rerenderVisiblePdfPages();
|
| 97 |
+
viewport.scrollLeft = horizontalCenter * viewport.scrollWidth - viewport.clientWidth / 2;
|
| 98 |
if (persist) scheduleSave();
|
| 99 |
}
|
| 100 |
for (const [id, delta] of [["#zoom-out", -10], ["#zoom-in", 10]]) {
|
|
|
|
| 226 |
try {
|
| 227 |
const page = await pdfDocument.getPage(Number(shell.dataset.page));
|
| 228 |
const base = page.getViewport({ scale: 1 });
|
| 229 |
+
const scale = Math.min(3, Math.max(0.5, shell.clientWidth / base.width));
|
| 230 |
const rendered = page.getViewport({ scale });
|
| 231 |
const canvas = shell.querySelector("canvas");
|
| 232 |
canvas.width = rendered.width; canvas.height = rendered.height;
|
|
|
|
| 381 |
}
|
| 382 |
async function displayEpub(url, frame) {
|
| 383 |
const book = ePub(url); epubRendition = book.renderTo(frame, { width: "100%", height: "100%", spread: "none", flow: "scrolled-doc" });
|
| 384 |
+
epubRendition.themes.fontSize(`${Math.round(zoom * 100)}%`);
|
| 385 |
epubRendition.on("relocated", (location) => { epubLocation = location && location.start ? location.start.cfi : ""; scheduleSave(); });
|
| 386 |
const restoredLocation = restoredEntry && restoredEntry.epubLocation;
|
| 387 |
try { await epubRendition.display(restoredLocation || undefined); }
|
static/style.css
CHANGED
|
@@ -944,19 +944,42 @@ body.mobile .multi-toggle { padding: 1px 4px; font-size: 11px; }
|
|
| 944 |
align-items: flex-start;
|
| 945 |
}
|
| 946 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 947 |
.result-action-btn {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 948 |
font-size: 11px;
|
|
|
|
| 949 |
padding: 3px 8px;
|
| 950 |
border-radius: var(--radius-sm);
|
| 951 |
color: var(--on-surface-variant);
|
| 952 |
border: 1px solid var(--outline-variant);
|
| 953 |
white-space: nowrap;
|
|
|
|
| 954 |
transition: all var(--transition);
|
| 955 |
}
|
| 956 |
|
| 957 |
.result-action-btn:hover, .result-action-btn:focus-visible {
|
| 958 |
background: var(--surface-variant);
|
| 959 |
border-color: var(--outline);
|
|
|
|
| 960 |
}
|
| 961 |
|
| 962 |
.result-action-btn.primary {
|
|
|
|
| 944 |
align-items: flex-start;
|
| 945 |
}
|
| 946 |
|
| 947 |
+
body:not(.mobile) .result-item {
|
| 948 |
+
display: grid;
|
| 949 |
+
grid-template-columns: auto auto minmax(0, 1fr) auto;
|
| 950 |
+
grid-template-rows: auto auto auto;
|
| 951 |
+
column-gap: 10px;
|
| 952 |
+
row-gap: 3px;
|
| 953 |
+
}
|
| 954 |
+
|
| 955 |
+
body:not(.mobile) .result-checkbox { grid-column: 1; grid-row: 1 / 4; align-self: start; margin-top: 4px; }
|
| 956 |
+
body:not(.mobile) .result-file-icon { grid-column: 2; grid-row: 1 / 4; }
|
| 957 |
+
body:not(.mobile) .result-info { display: contents; }
|
| 958 |
+
body:not(.mobile) .result-title { grid-column: 3; grid-row: 1; min-width: 0; }
|
| 959 |
+
body:not(.mobile) .result-actions { grid-column: 4; grid-row: 1; justify-content: flex-end; }
|
| 960 |
+
body:not(.mobile) .result-path { grid-column: 3 / 5; grid-row: 2; margin-top: 0; min-width: 0; }
|
| 961 |
+
body:not(.mobile) .result-meta { grid-column: 3 / 5; grid-row: 3; margin-top: 0; min-width: 0; }
|
| 962 |
+
|
| 963 |
.result-action-btn {
|
| 964 |
+
height: 24px;
|
| 965 |
+
display: inline-flex;
|
| 966 |
+
align-items: center;
|
| 967 |
+
justify-content: center;
|
| 968 |
font-size: 11px;
|
| 969 |
+
line-height: 1;
|
| 970 |
padding: 3px 8px;
|
| 971 |
border-radius: var(--radius-sm);
|
| 972 |
color: var(--on-surface-variant);
|
| 973 |
border: 1px solid var(--outline-variant);
|
| 974 |
white-space: nowrap;
|
| 975 |
+
text-decoration: none;
|
| 976 |
transition: all var(--transition);
|
| 977 |
}
|
| 978 |
|
| 979 |
.result-action-btn:hover, .result-action-btn:focus-visible {
|
| 980 |
background: var(--surface-variant);
|
| 981 |
border-color: var(--outline);
|
| 982 |
+
text-decoration: none;
|
| 983 |
}
|
| 984 |
|
| 985 |
.result-action-btn.primary {
|