diff options
Diffstat (limited to 'src/features')
| -rw-r--r-- | src/features/messages.js | 77 |
1 files changed, 57 insertions, 20 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 8576c46..59133dc 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -4320,7 +4320,7 @@ export function messagesFeature(ctx) { // Registered at CONSTRUCTION, not init(): the public no-wallet profile // (a /username deep link over the unlock screen) scrolls too, and init // only runs once a wallet opens. - let lastFeedY = 0; + let lastFeedY = 0, pillFrame = 0; if (typeof window !== 'undefined') { window.addEventListener('scroll', () => { if (!ui.profilePk && (!(ui.chatOpen && ui.msgView === 'feed') || ui.noteThread)) return; @@ -4332,8 +4332,17 @@ export function messagesFeature(ctx) { // DOWN, and that must not count as having gone up to look at them. const y = window.scrollY || 0, up = y < lastFeedY; lastFeedY = y; - if (!ui.profilePk && feed && feed.unseen && up && atFeedTop() - && !feed.deferred?.some((e) => feed.fresh?.has(e.id))) { feed.unseen = 0; render(); } + // Scrolling recounts the pill: posts you've gone up past drop off it, + // and once none are left above you the notice is done. + if (!ui.profilePk && feed && feed.ahead?.size && up && !pillFrame) pillFrame = requestAnimationFrame(() => { + pillFrame = 0; + if (!feed) return; + markReached(feed); + if (!feed.unseen) return; + const n = pillCount(feed); + if (!n) { feed.unseen = 0; render(); } + else if (n !== feed.pillN) render(); + }); if (!ui.profilePk && feed?.deferred?.length) admitFeed(feed.deferred, feed); if (window.innerHeight + window.scrollY < (document.documentElement.scrollHeight || 0) - (ui.profilePk ? 600 : Math.max(2400, window.innerHeight * 3))) return; if (ui.profilePk) loadOlderNotes(ui.profilePk).catch(() => {}); @@ -5459,7 +5468,7 @@ export function messagesFeature(ctx) { ui.feedId = id; stopFeedWatch(); const c = feedNow(); - c.unseen = 0; c.fresh = null; c.shown = FEED_PAGE; + c.unseen = 0; c.fresh = null; c.ahead = null; c.shown = FEED_PAGE; admitFeed(c.deferred || [], c, true); // An already warmed feed can paint synchronously on entry. if (!c.booting) c.presentations.clear(); @@ -5679,17 +5688,6 @@ export function messagesFeature(ctx) { ui.chatOpen = true; ui.msgView = 'feed'; switchFeed(id); } - // Reaching the newest post manually clears the new-post notice. Not a - // scroll offset: new posts go in above the reader with the page held, so - // the page is pushed off the top by however tall they are, and a short - // one would leave scrollY under any fixed threshold unseen. - const atFeedTop = () => { - try { - const top = document.querySelector('.notes-feed > .row[data-key]'); - return !!top && !document.querySelector('.notes-feed > .feed-spacer[data-key="win-top"]') - && top.getBoundingClientRect().top > -40; - } catch { return true; } - }; // Something above the posts is in use — a post being written, the relay // panel, a focused field — so new posts wait behind the pill rather than // push it off the screen. @@ -6294,6 +6292,7 @@ export function messagesFeature(ctx) { const known = new Set(c.notes.map((e) => e.id)); for (const e of add) if (e.created_at > newerThan && !known.has(e.id) && !c.fresh.has(e.id)) { c.fresh.add(e.id); + (c.ahead ||= new Set()).add(e.id); c.unseen = (c.unseen || 0) + 1; noticed = true; } @@ -6373,16 +6372,54 @@ export function messagesFeature(ctx) { }, }; } + // Where you are in the feed: the rows on screen, top first, as indexes + // into the list (none when the feed isn't showing). + function feedRowsOnScreen() { + if (!(ui.chatOpen && ui.msgView === 'feed') || ui.profilePk || ui.noteThread) return []; + const out = []; + for (const r of document.querySelectorAll('.notes-feed > .row[data-key]')) { + const b = r.getBoundingClientRect(); + if (b.bottom > 8) out.push({ id: r.getAttribute('data-key'), top: b.top }); + } + return out; + } + // c.ahead: new posts you haven't scrolled up to yet. The pill counts + // them; going up to one (or past it) takes it, and everything below it, + // off. The tint (c.fresh) is separate — it fades on first sight, and a + // post that went in above you shows a sliver of itself without you + // having gone up there. + function markReached(c) { + if (!c || !c.ahead || !c.ahead.size) return; + const top = feedRowsOnScreen().find((r) => r.top >= -8); + if (!top) return; + const list = c.winList ? c.winList() : []; + const k = list.findIndex((ev) => ev.id === top.id); + if (k >= 0) for (let j = k; j < list.length; j++) c.ahead.delete(list[j].id); + } + function pillCount(c) { + if (!c || !c.unseen || !c.ahead || !c.ahead.size) return 0; + let n = 0; + for (const ev of c.notes) if (c.ahead.has(ev.id) && !hidden(ev)) n++; + for (const ev of c.deferred || []) if (c.ahead.has(ev.id)) n++; + return n; + } + // Only ever up: to the nearest new post above the one you're reading. + // New posts below it are already behind you. function jumpToNew() { const c = feed; if (!c) return; + // the first row whose top is on screen, as markReached reads it: one + // peeking in above that is still up there to go to + const at = feedRowsOnScreen().find((r) => r.top >= -8)?.id; admitFeed(c.deferred || [], c, true); c.unseen = 0; render(); const list = c.winList ? c.winList() : []; + let r = at ? list.findIndex((ev) => ev.id === at) : -1; + if (r < 0) r = list.length; let i = -1; - for (let j = list.length - 1; j >= 0; j--) if (c.fresh && c.fresh.has(list[j].id)) { i = j; break; } - if (i < 0) { glideToTop(); return; } + for (let j = r - 1; j >= 0; j--) if (c.ahead && c.ahead.has(list[j].id)) { i = j; break; } + if (i < 0) { if (r === list.length || !at) glideToTop(); return; } glideToPost(c, list, i); } // Scroll so post i's top sits just under the top of the window. A row @@ -11080,9 +11117,9 @@ export function messagesFeature(ctx) { // the count itself is a fresh node each time, which is what replays its // bump. The tap is the pill's alone (no bubbling into the page). // A swipe sends it away until more posts arrive than it was showing. - const waiting = c.unseen || 0; - if (!waiting) c.pillDismissed = 0; - const pill = waiting > (c.pillDismissed || 0) + const waiting = c.pillN = pillCount(c); + if (!c.unseen) c.pillDismissed = 0; + const pill = waiting && (c.unseen || 0) > (c.pillDismissed || 0) ? h('button', { class: 'feed-new-pill', 'data-key': 'feed-pill', type: 'button', onClick: (e) => { |
