Moments across up to four pages: stacked in Flow, side by side in Page/Book (BL-0055)

The save sheet gets "+ Previous page" / "+ Next page" (right to left for
rtl in Page/Book) for a picture that runs over several pages. Moments
store a layout (row | column); pages the selected region does not touch
are dropped. Crops, viewer and gallery draw both layouts.

Dialogs now clean up directly instead of waiting for the "close" event,
which a backgrounded page can deliver late (a closed sheet stayed in the
page and M stopped working).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MaashkinoozheandClaude Opus 5.5 committed 2026-10-27 12:38:22 +01:00
1 parent 74842c59e0
commit 5497e8bfbc
9 files changed
+175 -92

No files matched your search

+19 -17
View File
@@ -502,39 +502,41 @@
if (action) keyActions[action](e);
});
function showKeys() {
const dialog = h('dialog', { class: 'moment-dialog key-dialog' },
const dialog = openDialog('key-dialog', () => stage.focus({ preventScroll: true }));
dialog.append(
h('div', { class: 'moment-head' }, h('h2', {}, 'Keyboard shortcuts'),
h('button', { class: 'btn icon', type: 'button', 'aria-label': 'Close', onclick: () => dialog.close() }, '×')),
h('button', { class: 'btn icon', type: 'button', 'aria-label': 'Close', onclick: () => dialog.done() }, '×')),
keyTable([{ title: 'Reader', keys: READER_KEYS }]),
h('p', { class: 'muted small' }, 'Also listed in Settings → Keyboard shortcuts.'));
dialog.addEventListener('close', () => { dialog.remove(); stage.focus({ preventScroll: true }); });
document.body.append(dialog);
dialog.showModal();
}
// ---------- moments ----------
const momentPages = new Set((D.moments || []).flatMap(x => x.pages));
// The page(s) on screen, left to right as displayed. In Flow the page at the reading line.
const momentPage = p => ({ i: p.i, u: p.u, w: D.pages[p.i].w, h: D.pages[p.i].h, r: p.r });
// Positions in `list` of the page(s) on screen, in reading order. In Flow the page at the reading line.
function currentRange() {
if (atEnd && mode !== 'flow') return null;
const ks = (mode === 'book' ? shown : [cur]).filter(k => list[k]);
return ks.length ? [Math.min(...ks), Math.max(...ks)] : null;
}
function currentPages() {
if (atEnd && mode !== 'flow') return [];
const ks = mode === 'book' ? shown.slice() : [cur];
if (mode === 'book' && rtl) ks.reverse();
return ks.filter(k => list[k]).map(k => {
const p = list[k], raw = D.pages[p.i];
return { i: p.i, u: p.u, w: raw.w, h: raw.h, r: p.r };
});
const range = currentRange();
return range ? list.slice(range[0], range[1] + 1).map(momentPage) : [];
}
let momentOpen = false;
async function saveMoment() {
const pages = currentPages();
if (!pages.length) return toast('No page here');
const range = currentRange();
if (!range) return toast('No page here');
if (momentOpen) return;
momentOpen = true;
const chapter = D.chapters.find(c => c.id === D.chapter.id), book = D.books.length > 1 && chapter && D.books.find(b => b.key === chapter.book);
// A picture can run over several pages: the sheet can add neighbours, stacked in Flow, side by side in Page/Book.
const saved = await momentSheet({
heading: 'Save moment', pages,
context: [D.manga.title, [book && book.label, D.chapter.label + (D.chapter.title ? ' · ' + D.chapter.title : ''), momentPageLabel(pages)].filter(Boolean).join(' · ')],
save: async v => (await api('api/moments', { m: D.manga.id, c: D.chapter.id, v: D.version, pages: v.pages.map(p => p.i), region: v.region, cat: v.cat, note: v.note })).moment,
heading: 'Save moment', sequence: list.map(momentPage), range, layout: mode === 'flow' ? 'column' : 'row', rtl,
context: [D.manga.title, [book && book.label, D.chapter.label + (D.chapter.title ? ' · ' + D.chapter.title : '')].filter(Boolean).join(' · ')],
save: async v => (await api('api/moments', { m: D.manga.id, c: D.chapter.id, v: D.version, pages: v.pages.map(p => p.i), layout: v.layout,
region: v.region, cat: v.cat, note: v.note })).moment,
});
momentOpen = false;
stage.focus({ preventScroll: true });