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