Reader: Prozentanzeige mit Nachkommastelle bei langen Büchern (SW v27)
Bei sehr langen Büchern (ab ca. 1 Mio. Zeichen, entspricht etwa 600-700 Druckseiten) bewegt sich die ganzzahlige Prozentanzeige beim Lesen kaum noch — eine Nachkommastelle macht sie wieder aussagekräftig. Betrifft die Fortschrittsanzeige im Reader-Header sowie die "≈X% im Buch"-Koordinate bei Annotationen-Export und -Verzeichnis. Formatierung über toLocaleString() (Komma bei de, Punkt bei en, etc.). Das Fortschrittsfeld ist dafür von <input type="number"> auf type="text" mit inputmode="decimal" umgestellt — number-Inputs erzwingen intern immer einen Punkt, unabhängig von der Locale. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
7bfce034ba
commit
9648730be6
3 changed files with 32 additions and 12 deletions
|
|
@ -2769,8 +2769,28 @@ let currentImageMap = {};
|
|||
let readerScrollSaveTimer = null;
|
||||
let _resizeObserver = null;
|
||||
let _currentPositionAnchor = '';
|
||||
let _currentBookCharCount = 0;
|
||||
const bookMetaCache = {}; // id → {title, author, type}
|
||||
|
||||
// Reading-position percentages (the progress readout, annotation "% im Buch"
|
||||
// coordinates): a whole percent is fine for a normal book, but on a long one
|
||||
// (~600-700 physical pages and up) it barely moves during a whole scrolling
|
||||
// session, which reads as broken. Past that length, show one decimal —
|
||||
// formatted with the browser locale's own decimal separator.
|
||||
const LONG_BOOK_CHAR_THRESHOLD = 1000000;
|
||||
|
||||
function _formatBookPercent(fraction) {
|
||||
const pct = Math.max(0, Math.min(100, fraction * 100));
|
||||
const decimals = _currentBookCharCount > LONG_BOOK_CHAR_THRESHOLD ? 1 : 0;
|
||||
return pct.toLocaleString(undefined, {minimumFractionDigits: decimals, maximumFractionDigits: decimals});
|
||||
}
|
||||
|
||||
// Accepts both '45.3' and '45,3' (locale-typed input)
|
||||
function _parseLocalePercent(str) {
|
||||
const v = parseFloat(String(str).trim().replace(',', '.'));
|
||||
return isNaN(v) ? 0 : v;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reader "scrolled too far" safety net — offers a toast to jump back after a
|
||||
// sudden, large scroll (accidental fling, fat-fingered keybind, etc.). Only
|
||||
|
|
@ -3722,6 +3742,9 @@ async function openBook(bookId) {
|
|||
contentEl.appendChild(frag);
|
||||
await new Promise(r => requestAnimationFrame(r));
|
||||
}
|
||||
// Used to decide whole-percent vs one-decimal display further down —
|
||||
// see _formatBookPercent().
|
||||
_currentBookCharCount = contentEl.textContent.length;
|
||||
}
|
||||
|
||||
currentBookToc = toc;
|
||||
|
|
@ -3752,14 +3775,10 @@ async function openBook(bookId) {
|
|||
if (progressInput) {
|
||||
progressInput.style.display = '';
|
||||
if (isPdf) {
|
||||
progressInput.min = 1;
|
||||
progressInput.max = numPages;
|
||||
progressInput.value = 1;
|
||||
if (progressSuffix) progressSuffix.textContent = `/ ${numPages}`;
|
||||
} else {
|
||||
progressInput.min = 0;
|
||||
progressInput.max = 100;
|
||||
progressInput.value = 0;
|
||||
progressInput.value = _formatBookPercent(0);
|
||||
if (progressSuffix) progressSuffix.textContent = '%';
|
||||
}
|
||||
|
||||
|
|
@ -3774,8 +3793,8 @@ async function openBook(bookId) {
|
|||
contentEl.scrollBy({top: top - 8, behavior: 'smooth'});
|
||||
}
|
||||
} else {
|
||||
const pct = Math.min(100, Math.max(0, parseInt(this.value, 10) || 0));
|
||||
this.value = pct;
|
||||
const pct = Math.min(100, Math.max(0, _parseLocalePercent(this.value)));
|
||||
this.value = _formatBookPercent(pct / 100);
|
||||
contentEl.scrollTop = (pct / 100) * contentEl.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
|
@ -3847,7 +3866,7 @@ async function openBook(bookId) {
|
|||
progressInput.value = currentPage;
|
||||
} else {
|
||||
const f = contentEl.scrollTop / (contentEl.scrollHeight - contentEl.clientHeight || 1);
|
||||
progressInput.value = Math.round(f * 100);
|
||||
progressInput.value = _formatBookPercent(f);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -4061,6 +4080,7 @@ function closeReader() {
|
|||
// Reset all state
|
||||
currentBookId = null;
|
||||
currentBookToc = [];
|
||||
_currentBookCharCount = 0;
|
||||
currentPdfDoc = null;
|
||||
currentPdfBuffer = null;
|
||||
currentBookmarks = [];
|
||||
|
|
@ -5599,7 +5619,7 @@ function exportAnnotations() {
|
|||
lastChapter = chapterTitle;
|
||||
}
|
||||
const pct = _percentFromRange(range);
|
||||
const coord = pct !== null ? ` (≈${Math.round(pct)}% im Buch)` : '';
|
||||
const coord = pct !== null ? ` (≈${_formatBookPercent(pct / 100)}% im Buch)` : '';
|
||||
if (h.type === 'note') {
|
||||
lines.push(`[Note]${coord} near: "${(h.anchor?.quote || '').trim()}"`);
|
||||
if (h.note) lines.push(` ${h.note}`);
|
||||
|
|
@ -5635,7 +5655,7 @@ function openAnnotationsSidebar() {
|
|||
let html = '<ul style="list-style:none;padding:0;">';
|
||||
for (const {h, range} of resolved) {
|
||||
const pct = _percentFromRange(range);
|
||||
const pctLabel = pct !== null ? `≈${Math.round(pct)}%` : '';
|
||||
const pctLabel = pct !== null ? `≈${_formatBookPercent(pct / 100)}%` : '';
|
||||
const kind = h.type === 'note' ? 'Note' : `Highlight${h.color ? ' · ' + h.color : ''}`;
|
||||
const preview = h.note || h.anchor?.quote || '(no text)';
|
||||
html += `<li class="bookmark-entry">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* diora service worker — caches the app shell for offline use.
|
||||
*/
|
||||
|
||||
const CACHE = 'diora-v26';
|
||||
const CACHE = 'diora-v27';
|
||||
const PODCAST_CACHE = 'diora-podcast-v1';
|
||||
const SHELL = [
|
||||
'/static/css/app.css',
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@
|
|||
<span id="reader-title" class="reader-title"></span>
|
||||
<div class="reader-header-actions">
|
||||
<span class="reader-progress-wrap">
|
||||
<input type="number" id="reader-progress-input" class="volume-num" min="0" max="100" value="0" style="display:none;">
|
||||
<input type="text" inputmode="decimal" id="reader-progress-input" class="volume-num" value="0" style="display:none;">
|
||||
<span id="reader-progress-suffix" class="muted"></span>
|
||||
</span>
|
||||
<button class="btn-icon" id="reader-search-btn" onclick="toggleReaderSearch()" title="Search">⌕</button>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue