Reader: offene Notizleiste schiebt den Text nicht mehr aus der Mitte; sw v107
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 <noreply@anthropic.com>
This commit is contained in:
parent
656520fe8e
commit
a7d4d92f9e
7 changed files with 66 additions and 3 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = '';
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue