diff options
Diffstat (limited to 'src/features')
| -rw-r--r-- | src/features/messages.js | 58 |
1 files changed, 51 insertions, 7 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 61cdc18..6e977af 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -11994,7 +11994,7 @@ export function messagesFeature(ctx) { function dropPager() { const p = pager; pager = null; - try { p.io.disconnect(); p.near.disconnect(); } catch {} + try { p.io.disconnect(); p.near.disconnect(); p.tailIo.disconnect(); } catch {} clearInterval(p.tick); cancelAnimationFrame(p.raf); window.removeEventListener('resize', p.onResize); @@ -12114,6 +12114,8 @@ export function messagesFeature(ctx) { }; return bar; } + // the pager's slides, without the loading one at the end + const pagerSlides = () => (pager ? [...pager.scroller.children].filter((x) => !x._tail) : []); function pagerAttach(slide) { const m = slide.querySelector('.mp-media'); if (m && !m.getAttribute('src')) m.setAttribute('src', m.tagName === 'VIDEO' ? playSrc(m.getAttribute('data-src')) : m.getAttribute('data-src')); @@ -12136,7 +12138,7 @@ export function messagesFeature(ctx) { // clip holds nothing. function pagerNeighbours(slide) { if (!pager || pager.kind !== 'videos') return; - const slides = [...pager.scroller.children]; + const slides = pagerSlides(); const i = slides.indexOf(slide); const eager = !frugalData(); slides.forEach((s, j) => { @@ -12265,7 +12267,7 @@ export function messagesFeature(ctx) { h('span', { class: 'on' }, '\u{1F50A}'), h('span', { class: 'off' }, '\u{1F507} ' + t('videoUnmute'))) : null)); // a tap shows (or hides) who posted it and what they said el.addEventListener('click', (e) => { - if (e.target.closest('button, a, .mp-caption, .mp-seek')) return; + if (e.target.closest('button, a, .mp-caption, .mp-seek, .mp-tail')) return; if (Date.now() - videoTapAt < 800) return; // the click of the touch that opened it if (pager && pager.fromFeed && video) { // A clip opened from the feed: a tap pauses it and brings up who @@ -12291,6 +12293,32 @@ export function messagesFeature(ctx) { if (el.classList.contains('show')) pagerCounts(pager && pager.cur); }); pager = { el, scroller, kind: want.kind, keys: new Set(), cur: null, feed: want.feed, fromFeed: !!want.fromFeed, start: want.start, at: want.at || 0 }; + // The end of the pager: swiping past the last slide lands here, on + // something saying more is coming (not a nudge that goes nowhere) — and + // asks for it at once. Slides that arrive go in above it; one arriving + // while the reader waits here is moved onto, and plays. With nothing + // more to be had, it says so. + const tail = h('div', { class: 'mp-tail', 'aria-live': 'polite' }, + h('div', { class: 'mp-loader', 'aria-hidden': 'true', html: video + ? '<span class="mp-orbit"><i></i><i></i><i></i></span><span class="mp-core"><svg viewBox="0 0 24 24" width="30" height="30"><path d="M8 5.5v13l10.5-6.5z" fill="currentColor"/></svg></span>' + : '<span class="mp-orbit"><i></i><i></i><i></i></span><span class="mp-core"><svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><rect x="3.5" y="4.5" width="17" height="15" rx="2.5"/><path d="M3.5 16l5-5 4 4 2.5-2.5 5 5"/><circle cx="15.5" cy="9" r="1.6" fill="currentColor" stroke="none"/></svg></span>' }), + h('div', { class: 'mp-tail-text' }, t(video ? 'pagerFetchingVideo' : 'pagerFetchingPictures'))); + tail._tail = true; + scroller.append(tail); + pager.tail = tail; + pager.tailIo = new IntersectionObserver((entries) => { + for (const e of entries) { + if (!pager) return; + pager.onTail = e.isIntersecting && e.intersectionRatio >= 0.6; + if (!pager.onTail) continue; + const v = pager.cur && pager.cur.querySelector('video.mp-media'); + if (v) v.pause(); + el.classList.remove('show'); + if (pager.fromFeed) pagerFeedAhead(); else crawlMedia().catch(() => {}); + pagerTailState(); + } + }, { root: scroller, threshold: [0, 0.6] }); + pager.tailIo.observe(tail); pager.near = new IntersectionObserver((entries) => { for (const e of entries) { if (!e.isIntersecting) continue; @@ -12313,13 +12341,13 @@ export function messagesFeature(ctx) { pagerCounts(e.target); // near the end: ask the feed for more, the pager picks them up if (pager.fromFeed) pagerFeedAhead(); - else { const slides = scroller.children; if ([...slides].indexOf(e.target) >= slides.length - 3) crawlMedia().catch(() => {}); } + else { const slides = pagerSlides(); if (slides.indexOf(e.target) >= slides.length - 3) crawlMedia().catch(() => {}); } } }, { root: scroller, threshold: [0.6] }); growPager(); document.body.append(el); document.documentElement.classList.add('no-scroll'); - const first = [...scroller.children].find((s) => s.getAttribute('data-k') === want.start) || scroller.firstChild; + const first = pagerSlides().find((s) => s.getAttribute('data-k') === want.start) || scroller.firstChild; if (first) scroller.scrollTop = first.offsetTop; // the clip on screen's seek bar, every frame (timeupdate comes a few times a second) if (video) { @@ -12349,7 +12377,7 @@ export function messagesFeature(ctx) { try { const c = feedNow(); for (let pages = 0; pages < 8 && pager && pager.fromFeed; pages++) { - const slides = [...pager.scroller.children]; + const slides = pagerSlides(); if (slides.length - 1 - slides.indexOf(pager.cur) > (PAGER_AHEAD[pager.kind] || 4)) break; const shown = c.shown, held = c.notes.length; await loadOlderFeed(); @@ -12363,15 +12391,31 @@ export function messagesFeature(ctx) { function growPager() { if (!pager) return; const c = feedNow(); + const waiting = pager.onTail; + let first = null; for (const it of (pager.fromFeed ? feedMediaItems(c, pager.kind) : mediaItems(c, pager.kind))) { const k = mediaKey(it); if (pager.keys.has(k)) continue; pager.keys.add(k); const s = pagerSlide(it); - pager.scroller.append(s); + pager.scroller.insertBefore(s, pager.tail || null); + first ||= s; pager.near.observe(s); pager.io.observe(s); } + // the reader was waiting at the end: onto the first that came + if (waiting && first) { pager.onTail = false; pager.scroller.scrollTop = first.offsetTop; } + pagerTailState(); + } + // the end says "more coming", or — the feed (or the media index) has no + // more to give — that this is everything + function pagerTailState() { + if (!pager || !pager.tail) return; + const c = feedNow(); + const done = pager.fromFeed ? !!c.end && c.shown >= c.notes.length && !c.loadingMore : !!mediaIndex(c.id).end; + if (pager.tail.classList.contains('done') === done) return; + pager.tail.classList.toggle('done', done); + pager.tail.querySelector('.mp-tail-text').textContent = done ? t('pagerCaughtUp') : t(pager.kind === 'videos' ? 'pagerFetchingVideo' : 'pagerFetchingPictures'); } if (typeof window !== 'undefined') { window.addEventListener('keydown', (e) => { |
