aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js58
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) => {