Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b45092bc8 |
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"VERSION": "v0.7.27",
|
||||
"VERSION_NUMBER": "0.7.27",
|
||||
"BUILD_DATE": "2026-06-26T00:00:43.641Z"
|
||||
"VERSION": "v0.7.28",
|
||||
"VERSION_NUMBER": "0.7.28",
|
||||
"BUILD_DATE": "2026-06-26T00:20:10.087Z"
|
||||
}
|
||||
|
||||
+111
-27
@@ -575,8 +575,11 @@
|
||||
if (!evt?.tags) return false;
|
||||
const eTags = evt.tags.filter((t) => t[0] === 'e' && t[1]);
|
||||
if (eTags.length === 0) return false;
|
||||
// A reply is any kind 1 that references the target event id in an e tag.
|
||||
return eTags.some((t) => t[1] === targetEventId);
|
||||
// A reply is any kind 1 that references the root post OR any already-known
|
||||
// reply in the thread (nested replies only carry their immediate parent,
|
||||
// not the root post id). This keeps the thread inclusive of replies-to-replies.
|
||||
if (eTags.some((t) => t[1] === targetEventId)) return true;
|
||||
return eTags.some((t) => replyIds.has(t[1]));
|
||||
}
|
||||
|
||||
function upsertReply(evt) {
|
||||
@@ -850,11 +853,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function appendReplyIfNew(evt) {
|
||||
if (!upsertReply(evt)) return;
|
||||
renderReplies();
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
BOTTOM REPLY COMPOSER
|
||||
================================================================
|
||||
@@ -964,27 +962,104 @@
|
||||
});
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
fetchRepliesRecursive(rootEventId, maxDepth)
|
||||
----------------------------------------------------------------
|
||||
Recursively fetches replies to the root post AND replies to those
|
||||
replies (nested replies), up to maxDepth levels. Nested replies
|
||||
only carry their immediate parent in their `e` tags — not the root
|
||||
post id — so a single { '#e': [rootId] } query misses them.
|
||||
|
||||
Strategy (breadth-first by depth level):
|
||||
depth 0: fetch replies to rootEventId
|
||||
depth 1: fetch replies to every reply found at depth 0
|
||||
depth 2: fetch replies to every reply found at depth 1
|
||||
... up to maxDepth
|
||||
|
||||
The `#e` filter accepts an array of ids, so we batch all ids at
|
||||
the current depth into a single fetch. Dedup via replyIds so a
|
||||
reply discovered via multiple parent paths is only added once.
|
||||
|
||||
Both the cache (queryCache) and relays (ndkFetchEvents) are
|
||||
consulted at each level — cache first for fast display, then
|
||||
relays for hydration. A per-level timeout guards against
|
||||
excessive relay requests on very long threads.
|
||||
---------------------------------------------------------------- */
|
||||
const REPLY_FETCH_MAX_DEPTH = 5;
|
||||
const REPLY_FETCH_LEVEL_TIMEOUT_MS = 8000;
|
||||
|
||||
function fetchRepliesForLevelFromCache(parentIds) {
|
||||
return queryCache({ kinds: [1], '#e': parentIds }).catch(() => []).then((r) => Array.isArray(r) ? r : []);
|
||||
}
|
||||
|
||||
function fetchRepliesForLevelFromRelays(parentIds) {
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
const timer = setTimeout(() => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
resolve([]);
|
||||
}, REPLY_FETCH_LEVEL_TIMEOUT_MS);
|
||||
|
||||
ndkFetchEvents({ kinds: [1], '#e': parentIds })
|
||||
.then((events) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
resolve(Array.isArray(events) ? events : []);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('[post-feed.html] Recursive reply level fetch failed:', error?.message || error);
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
resolve([]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchRepliesRecursive(rootEventId, maxDepth = REPLY_FETCH_MAX_DEPTH) {
|
||||
if (!rootEventId) return;
|
||||
|
||||
let currentLevelIds = [rootEventId];
|
||||
|
||||
for (let depth = 0; depth < maxDepth; depth += 1) {
|
||||
if (currentLevelIds.length === 0) break;
|
||||
|
||||
// Cache first — fast display of whatever we already have.
|
||||
const cachedEvents = await fetchRepliesForLevelFromCache(currentLevelIds);
|
||||
const nextLevelIds = [];
|
||||
for (const evt of cachedEvents) {
|
||||
if (upsertReply(evt)) {
|
||||
nextLevelIds.push(evt.id);
|
||||
}
|
||||
}
|
||||
if (cachedEvents.length > 0) renderReplies();
|
||||
|
||||
// Then relay hydration for this level.
|
||||
const relayEvents = await fetchRepliesForLevelFromRelays(currentLevelIds);
|
||||
for (const evt of relayEvents) {
|
||||
if (upsertReply(evt)) {
|
||||
nextLevelIds.push(evt.id);
|
||||
}
|
||||
}
|
||||
if (relayEvents.length > 0) renderReplies();
|
||||
|
||||
// Move to the next depth level: replies to the replies we just found.
|
||||
// (upsertReply dedupes via replyIds, so ids already seen are skipped
|
||||
// on the next iteration by virtue of not being newly added — but we
|
||||
// still need to query for their children, so include all newly-found
|
||||
// ids here.)
|
||||
currentLevelIds = nextLevelIds;
|
||||
}
|
||||
|
||||
// Final render to ensure consistency.
|
||||
renderReplies();
|
||||
}
|
||||
|
||||
async function fetchReplies() {
|
||||
if (!targetEventId) return;
|
||||
|
||||
// Cache first.
|
||||
try {
|
||||
const cachedReplies = await queryCache({ kinds: [1], '#e': [targetEventId] });
|
||||
if (Array.isArray(cachedReplies)) {
|
||||
cachedReplies.forEach((evt) => upsertReply(evt));
|
||||
renderReplies();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
// Then relay fetch.
|
||||
void ndkFetchEvents({ kinds: [1], '#e': [targetEventId] }).then((fresh) => {
|
||||
if (Array.isArray(fresh)) {
|
||||
fresh.forEach((evt) => upsertReply(evt));
|
||||
}
|
||||
renderReplies();
|
||||
}).catch((error) => {
|
||||
console.warn('[post-feed.html] Reply fetch failed:', error?.message || error);
|
||||
});
|
||||
await fetchRepliesRecursive(targetEventId, REPLY_FETCH_MAX_DEPTH);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
@@ -1153,7 +1228,16 @@
|
||||
}
|
||||
|
||||
if (isReplyToThread(evt)) {
|
||||
appendReplyIfNew(evt);
|
||||
const wasNew = upsertReply(evt);
|
||||
if (wasNew) {
|
||||
renderReplies();
|
||||
// A newly-seen reply may itself have nested replies we haven't
|
||||
// fetched yet. Trigger a one-level-deep fetch for replies to it
|
||||
// so nested replies appear in realtime too.
|
||||
fetchRepliesRecursive(evt.id, 1).catch((err) => {
|
||||
console.warn('[post-feed.html] Realtime nested fetch failed:', err?.message || err);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user