aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/features/messages.js50
-rw-r--r--tools/feed-pager-ahead-test.js19
2 files changed, 59 insertions, 10 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index 2580ed9..9cba810 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -11934,12 +11934,48 @@ export function messagesFeature(ctx) {
let tries = 0;
const go = () => {
if (ui.mediaPager || !document.querySelector('.notes-feed')) { if (++tries < 90) requestAnimationFrame(go); return; }
- const list = c.winList ? c.winList() : c.notes;
- const i = list.findIndex((e) => e.id === rowId);
- if (i >= 0) glideToPost(c, list, i);
+ landOnPost(c, rowId);
};
requestAnimationFrame(go);
}
+ // Put the feed on a post the reader hasn't been near — one the pager went
+ // on to, maybe paged in while it was open and never drawn. Its place is
+ // only a guess until it is drawn, and the rows around it settle as they
+ // draw (a long post, a picture finding its size), so: jump to the guess,
+ // draw, and set the row under the top again each frame — until it has sat
+ // still a moment, a couple of seconds have gone, or the reader touches the
+ // page. A jump, not a glide: the pager was covering the feed.
+ function landOnPost(c, rowId) {
+ const MARGIN = 8, t0 = Date.now();
+ const find = () => document.querySelector('.notes-feed > [data-key="' + CSS.escape(rowId) + '"]');
+ let still = 0, misses = 0, lastY = null;
+ const step = () => {
+ if (c !== feed || ui.mediaPager || !document.querySelector('.notes-feed') || lastInputAt > t0 || Date.now() - t0 > 2500) return;
+ // the page sent somewhere else meanwhile (not by a finger: a glide, a link)
+ if (lastY !== null && Math.abs(window.scrollY - lastY) > window.innerHeight / 2) return;
+ const el = find();
+ if (!el) {
+ const list = c.winList ? c.winList() : c.notes;
+ const i = list.findIndex((e) => e.id === rowId);
+ if (i < 0 || ++misses > 20) return;
+ let sum = 0, n = 0;
+ for (const v of (c.heights || new Map()).values()) { sum += v; n++; }
+ const avg = n ? sum / n : 200;
+ let y = 0;
+ for (let j = 0; j < i; j++) y += ((c.heights && c.heights.get(list[j].id)) || avg) + ROW_GAP;
+ window.scrollTo(0, Math.max(0, (c.listTop || 0) + y - MARGIN));
+ render();
+ requestAnimationFrame(step);
+ return;
+ }
+ const off = el.getBoundingClientRect().top - MARGIN;
+ if (Math.abs(off) > 2) { window.scrollTo(0, Math.max(0, window.scrollY + off)); still = 0; }
+ else if (++still >= 20 && Date.now() - t0 > 600) return;
+ lastY = window.scrollY;
+ requestAnimationFrame(step);
+ };
+ step();
+ }
// Leaving the pager for another page (a profile, sign-in, the zap amount):
// only the feed's own render syncs the pager, and those pages don't paint
// the feed — so the overlay goes now, not on the next feed render.
@@ -12262,11 +12298,13 @@ export function messagesFeature(ctx) {
}
// A pager opened from the feed walks the feed's own posts, which page in
// ten at a time as the feed is scrolled. Swiping is the scrolling here:
- // with only a few slides left past the one on screen, the feed pages on
+ // with only a few slides left past the one on screen (PAGER_AHEAD), the feed pages on
// (the posts it holds, then older ones from the relays) until there are
// more — several pages if those posts carry none. A page that brings
// nothing (the feed busy, or at its end) stops it; the tick tries again.
- const PAGER_AHEAD = 3;
+ // how many slides ahead to keep: clips are fewer and further between
+ // (more pages to find the next) and each is watched a while, so further
+ const PAGER_AHEAD = { images: 4, videos: 6 };
let pagerFeeding = false;
async function pagerFeedAhead() {
if (pagerFeeding || !pager || !pager.fromFeed) return;
@@ -12275,7 +12313,7 @@ export function messagesFeature(ctx) {
const c = feedNow();
for (let pages = 0; pages < 8 && pager && pager.fromFeed; pages++) {
const slides = [...pager.scroller.children];
- if (slides.length - 1 - slides.indexOf(pager.cur) > PAGER_AHEAD) break;
+ if (slides.length - 1 - slides.indexOf(pager.cur) > (PAGER_AHEAD[pager.kind] || 4)) break;
const shown = c.shown, held = c.notes.length;
await loadOlderFeed();
growPager();
diff --git a/tools/feed-pager-ahead-test.js b/tools/feed-pager-ahead-test.js
index b5a20ba..504c122 100644
--- a/tools/feed-pager-ahead-test.js
+++ b/tools/feed-pager-ahead-test.js
@@ -1,5 +1,5 @@
// Swiping through the feed's pictures (or clips) in the pager is scrolling
-// the feed: with three slides or fewer left past the one on screen, the
+// the feed: with four slides or fewer left past the one on screen (six for clips), the
// feed pages on, and the pager grows — past a run of posts with nothing to
// show, too — so the reader never meets the end while there is more.
// Run: bun tools/feed-pager-ahead-test.js
@@ -28,7 +28,7 @@ const U = (n) => server.url.origin + '/p' + n + '.png';
const seeded = [];
let ago = 100, pic = 0;
for (let i = 0; i < 12; i++) seeded.push(post('picture number ' + pic + ' ' + U(pic++), ago += 60));
-for (let i = 0; i < 15; i++) seeded.push(post('just words ' + i + ' ' + 'lorem ipsum dolor sit amet. '.repeat(3), ago += 60));
+for (let i = 0; i < 15; i++) seeded.push(post('just words ' + i + ' ' + 'lorem ipsum dolor sit amet. '.repeat(i % 3 ? 3 : 90), ago += 60)); // some of them long: rows of very different heights
for (let i = 0; i < 14; i++) seeded.push(post('picture number ' + pic + ' ' + U(pic++), ago += 60));
const TOTAL = pic;
@@ -126,9 +126,9 @@ try {
for (let step = 0; step < TOTAL + 4; step++) {
const n = await slides(), i = await curIdx();
maxSlides = Math.max(maxSlides, n);
- if (i >= 0 && n - 1 - i <= 3) {
+ if (i >= 0 && n - 1 - i <= 4) {
// give the look-ahead a moment: it should get past three ahead again (or the feed's end)
- await waitFor((k) => { const sc = document.querySelector('.mp-scroll'); const j = [...sc.children].findIndex((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return sc.children.length - 1 - j > 3 || sc.children.length >= k; }, TOTAL, 6000);
+ await waitFor((k) => { const sc = document.querySelector('.mp-scroll'); const j = [...sc.children].findIndex((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return sc.children.length - 1 - j > 4 || sc.children.length >= k; }, TOTAL, 6000);
}
const before = await curIdx();
await page.keyboard.press('ArrowDown');
@@ -139,6 +139,17 @@ try {
check('every picture in the feed became reachable', (await slides()) === TOTAL, (await slides()) + ' of ' + TOTAL);
check('…past fifteen posts with none', (await curName()) === 'p' + (TOTAL - 1) + '.png', await curName());
check('…in the feed\'s order', await page.evaluate((t) => [...document.querySelector('.mp-scroll').children].every((s, i) => s.getAttribute('data-k').endsWith('/p' + i + '.png')), TOTAL));
+
+ console.log('\n[closing lands the feed on the last picture\'s post]');
+ await page.keyboard.press('ArrowUp');
+ await waitFor((n) => { const sc = document.querySelector('.mp-scroll'); const s = [...sc.children].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return s && s.getAttribute('data-k').endsWith('/p' + n + '.png'); }, TOTAL - 2, 3000);
+ const want = 'picture number ' + (TOTAL - 2) + ' ';
+ await page.keyboard.press('Escape');
+ check('the pager closes', await waitFor(() => !document.querySelector('.media-pager') && !!document.querySelector('.notes-feed'), null, 3000));
+ const top = () => page.evaluate((w) => { const r = [...document.querySelectorAll('.notes-feed > [data-key]')].find((n) => n.textContent.includes(w)); return r ? Math.round(r.getBoundingClientRect().top) : null; }, want);
+ await sleep(2500); // the pictures above it load meanwhile
+ const t = await top();
+ check('…the feed sits on the post of the picture last looked at', t !== null && t >= -10 && t < 120, 'row top ' + t);
check('no page errors', errs.length === 0, errs.slice(0, 2).join(' | '));
} finally { await browser.close(); server.stop(true); }
console.log(ok ? '\n✅ the pager pages the feed on ahead of the reader' : '\n❌ failed');