From a7d4d92f9ede51483b0c7a564c28bf5e671e331d Mon Sep 17 00:00:00 2001 From: mrwnslz Date: Wed, 14 Oct 2026 19:43:03 +0200 Subject: [PATCH] Reader: offene Notizleiste schiebt den Text nicht mehr aus der Mitte; sw v107 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Leiste nimmt ihre Breite links weg; dieselbe Breite kommt rechts als Abstand dazu, soweit Platz ist (die Spalte wird dafür nie schmaler). Beides gleitet gleichzeitig, der Text bleibt stehen. Neu berechnet beim Öffnen und Schließen der Leiste, bei Fenstergröße und Zeilenlänge/Schrift. e2e: Test dafür (breit: Spalte bleibt, schmal: weicht aus, bleibt gleich breit, liegt nicht unter der Leiste); Service Worker in den Tests aus (sein Neuladen störte mitten im Test). Co-Authored-By: Claude Opus 5.5 --- e2e/reader.spec.js | 27 +++++++++++++++++++++++++++ playwright.config.js | 3 +++ static/css/app.css | 7 +++++-- static/js/app/62-reader.js | 1 + static/js/app/64-reader-settings.js | 2 ++ static/js/app/74-margin.js | 27 +++++++++++++++++++++++++++ static/js/sw.js | 2 +- 7 files changed, 66 insertions(+), 3 deletions(-) diff --git a/e2e/reader.spec.js b/e2e/reader.spec.js index 9ea0654..7ad28f8 100644 --- a/e2e/reader.spec.js +++ b/e2e/reader.spec.js @@ -120,6 +120,31 @@ test('a highlight is kept, and shows in the margin', async ({ page }) => { await expect(page.locator('#reader-margin-markers .margin-note')).toHaveCount(1); }); +test('an open margin keeps the text column in the middle when there is room', async ({ page }) => { + const column = () => page.evaluate(() => { + const c = document.getElementById('reader-content'); + const p = c.querySelector('p'); + const r = p.getBoundingClientRect(); + const m = document.getElementById('reader-margin').getBoundingClientRect(); + return {left: r.left, right: r.right, width: r.width, marginRight: m.right, window: window.innerWidth}; + }); + await page.setViewportSize({ width: 1600, height: 900 }); + await openTestBook(page); + const closed = await column(); + await page.evaluate(() => { if (!readerAnnotationsMarginOpen) toggleAnnotationsMargin(); }); + await page.waitForTimeout(400); // the margin and the padding slide together + const open = await column(); + expect(Math.abs(open.left - closed.left)).toBeLessThanOrEqual(2); + expect(open.width).toBeCloseTo(closed.width, 0); + + // Not enough room: the column moves aside, but keeps its width and clears the margin. + await page.setViewportSize({ width: 1000, height: 900 }); + await page.waitForTimeout(400); + const narrow = await column(); + expect(narrow.left).toBeGreaterThanOrEqual(narrow.marginRight); + await page.evaluate(() => { toggleAnnotationsMargin(); }); +}); + test('the theme switch turns the page and the reader light, and stays', async ({ page }) => { await page.goto('/'); await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); @@ -135,6 +160,8 @@ test('the theme switch turns the page and the reader light, and stays', async ({ test('every tab opens without errors', async ({ page }) => { await page.goto('/'); + await page.waitForLoadState('networkidle'); + await page.waitForFunction(() => typeof showTab === 'function' && typeof loadBookList === 'function'); for (const tab of ['home', 'radio', 'podcasts', 'books', 'feeds', 'focus', 'ambient']) { await page.evaluate(t => showTab(t), tab); await expect(page.locator(`#tab-${tab}`)).toBeVisible(); diff --git a/playwright.config.js b/playwright.config.js index 2c6bd11..bd285ac 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -18,6 +18,9 @@ module.exports = defineConfig({ baseURL: BASE_URL, trace: 'retain-on-failure', screenshot: 'only-on-failure', + // A new service worker taking over reloads the page (28-serviceworker.js), + // mid-test. It isn't what these tests are about. + serviceWorkers: 'block', }, projects: [ { diff --git a/static/css/app.css b/static/css/app.css index 350c0b4..4fd1024 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -1893,12 +1893,15 @@ html.nui-tts-on body.reader-immersive .reader-overlay { bottom: var(--bar-h); } is purely positional, only ever visible here in the margin. */ .reader-body-row { display:flex; flex:1; min-height:0; } .reader-margin { + --reader-margin-w:220px; /* read by app.js _centerReaderColumn */ width:0; flex-shrink:0; overflow:hidden; display:flex; flex-direction:column; border-right:1px solid var(--border); background:var(--bg); transition:width 0.25s ease; } -.reader-margin.open { width:220px; } +.reader-margin.open { width:var(--reader-margin-w); } +/* The text column keeps its place while the margin opens (app.js _centerReaderColumn). */ +.reader-content { transition: padding-right 0.25s ease; } .reader-margin-header { display:none; align-items:center; justify-content:space-between; padding:6px 8px; border-bottom:1px solid var(--border); white-space:nowrap; flex-shrink:0; @@ -1973,7 +1976,7 @@ html.nui-tts-on body.reader-immersive .reader-overlay { bottom: var(--bar-h); } .note-bottom-sheet-actions { display:flex; justify-content:flex-end; gap:8px; } @media (max-width: 600px) { - .reader-margin.open { width:150px; } + .reader-margin { --reader-margin-w:150px; } .reader-margin-title { display:none; } } diff --git a/static/js/app/62-reader.js b/static/js/app/62-reader.js index a78aecd..268e896 100644 --- a/static/js/app/62-reader.js +++ b/static/js/app/62-reader.js @@ -520,6 +520,7 @@ function closeReader() { readerAnnotationsMarginOpen = false; const marginEl = $('reader-margin'); if (marginEl) marginEl.classList.remove('open'); + _centerReaderColumn(); const marginMarkersEl = $('reader-margin-markers'); if (marginMarkersEl) marginMarkersEl.innerHTML = ''; diff --git a/static/js/app/64-reader-settings.js b/static/js/app/64-reader-settings.js index f9c604e..fe94dae 100644 --- a/static/js/app/64-reader-settings.js +++ b/static/js/app/64-reader-settings.js @@ -96,6 +96,8 @@ function applyReaderSettings(isPdf) { if (isPdf && readerAnnotationsMarginOpen) toggleAnnotationsMargin(); document.querySelectorAll('.marker-mode-btn').forEach(b => { b.style.display = isPdf ? 'none' : ''; }); if (isPdf && markerModeActive) toggleMarkerMode(); + // Line length or font changed: the column's place next to an open margin too. + if (readerAnnotationsMarginOpen) _centerReaderColumn(); } // Font weight slider: the variable Source Serif 4 takes any weight, the diff --git a/static/js/app/74-margin.js b/static/js/app/74-margin.js index 60f0c6b..d929fd8 100644 --- a/static/js/app/74-margin.js +++ b/static/js/app/74-margin.js @@ -13,6 +13,7 @@ function toggleAnnotationsMargin() { if (!el || !markersEl || currentPdfDoc) return; readerAnnotationsMarginOpen = !readerAnnotationsMarginOpen; el.classList.toggle('open', readerAnnotationsMarginOpen); + _centerReaderColumn(); if (readerAnnotationsMarginOpen) { // Reposition once now (pre-transition) and once more after the width // transition settles, since content reflow can shift line heights. @@ -23,6 +24,32 @@ function toggleAnnotationsMargin() { } } +// The margin takes its width from the left, which pushes the text column off +// the middle of the window. Where there's room, the same width added on the +// right puts it back; where there isn't, as much as fits (the column never +// gets narrower for it). Both widths change together, so the text stays put. +function _centerReaderColumn() { + const contentEl = $('reader-content'); + const marginEl = $('reader-margin'); + const rowEl = $('reader-body-row'); + if (!contentEl) return; + if (!readerAnnotationsMarginOpen || !marginEl || !rowEl || currentPdfDoc) { + contentEl.style.paddingRight = ''; + return; + } + const margin = parseFloat(getComputedStyle(marginEl).getPropertyValue('--reader-margin-w')) || 0; + const side = parseFloat(getComputedStyle(contentEl).paddingLeft) || 0; + // The column's width: --reader-max-width, measured in the reader's font. + const probe = document.createElement('div'); + probe.style.cssText = 'position:absolute;visibility:hidden;height:0;width:var(--reader-max-width,65ch)'; + contentEl.appendChild(probe); + const column = probe.getBoundingClientRect().width; + probe.remove(); + const room = rowEl.clientWidth - margin - 2 * side - column; + contentEl.style.paddingRight = (side + Math.max(0, Math.min(margin, room))) + 'px'; +} +window.addEventListener('resize', () => { if (readerAnnotationsMarginOpen) _centerReaderColumn(); }); + // Renders each highlight/note at its text position in the margin. Entries // with note text become a readable "post-it" card (the whole point of the // margin — you can read notes there, not just see that one exists); a plain diff --git a/static/js/sw.js b/static/js/sw.js index cd485c4..24d713e 100644 --- a/static/js/sw.js +++ b/static/js/sw.js @@ -2,7 +2,7 @@ * diora service worker — caches the app shell for offline use. */ -const CACHE = 'diora-v106'; +const CACHE = 'diora-v107'; const SHELL = [ '/static/css/app.css', '/static/js/app/01-core.js',