From 6d3918896716a57899d34ac043dba61445f57781 Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Thu, 25 Jun 2026 20:00:43 -0400 Subject: [PATCH] Fix post-feed.html: remove collapse, fix missing reply, move composer to top MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Removed collapse/expand functionality — all replies shown, no hidden counts 2. Fixed missing last reply — added safety check to ensure all fetched replies are rendered 3. Moved reply composer to top (under main post, before replies list) --- www/js/version.json | 6 +- www/post-feed.html | 233 ++++++++++++-------------------------------- 2 files changed, 64 insertions(+), 175 deletions(-) diff --git a/www/js/version.json b/www/js/version.json index d0807b1..67490f8 100644 --- a/www/js/version.json +++ b/www/js/version.json @@ -1,5 +1,5 @@ { - "VERSION": "v0.7.26", - "VERSION_NUMBER": "0.7.26", - "BUILD_DATE": "2026-06-25T23:53:36.767Z" + "VERSION": "v0.7.27", + "VERSION_NUMBER": "0.7.27", + "BUILD_DATE": "2026-06-26T00:00:43.641Z" } diff --git a/www/post-feed.html b/www/post-feed.html index dda7a7e..9437808 100644 --- a/www/post-feed.html +++ b/www/post-feed.html @@ -148,37 +148,6 @@ background: var(--accent-color); } - /* Click target overlaying each line so users can collapse a subtree - by clicking the vertical line for that level. */ - .reply-thread-line-hit { - position: absolute; - top: 0; - bottom: 0; - width: 12px; - cursor: pointer; - z-index: 1; - } - - .reply-collapse-toggle { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 2px 8px; - margin: 2px 0 4px 0; - font-size: 80%; - color: var(--muted-color); - background: var(--secondary-color); - border: 1px solid var(--muted-color); - border-radius: 8px; - cursor: pointer; - user-select: none; - } - - .reply-collapse-toggle:hover { - color: var(--accent-color); - border-color: var(--accent-color); - } - .reply-item-focused { outline: 2px solid var(--accent-color); outline-offset: 2px; @@ -205,10 +174,10 @@
Loading thread…
+
Replies
-
@@ -349,7 +318,6 @@ const replies = []; // flat list of reply events const replyIds = new Set(); // dedupe by event id const renderedReplyIds = new Set(); - const collapsedReplyIds = new Set(); // Phase 2.5 — collapsed subtree roots (in-memory) const focusedEventId = (urlParams.get('focus') || '').trim().toLowerCase() || null; let hamburgerInstance = null; @@ -742,80 +710,71 @@ to the next sibling. This makes vertical lines visually contiguous — a reply's descendants appear directly below it. - Returns an array of { event, level } in display order, skipping - descendants of any event id in `collapsedIds`. + Returns an array of { event, level } in display order. ---------------------------------------------------------------- */ - function buildDepthFirstOrder(events, levels, rootEventId, collapsedIds) { - const childrenOf = new Map(); // parentId -> [event, ...] - const rootChildren = []; + function buildDepthFirstOrder(events, levels, rootEventId) { + const childrenOf = new Map(); // parentId -> [event, ...] - for (const evt of events) { - if (!evt?.id || evt.id === rootEventId) continue; - const pid = getDirectParentId(evt); - let parentKey; - if (pid && events.some((e) => e?.id === pid)) { - parentKey = pid; - } else { - // Parent not in set — group under root. - parentKey = rootEventId; - } - if (!childrenOf.has(parentKey)) childrenOf.set(parentKey, []); - childrenOf.get(parentKey).push(evt); - } + for (const evt of events) { + if (!evt?.id || evt.id === rootEventId) continue; + const pid = getDirectParentId(evt); + let parentKey; + if (pid && events.some((e) => e?.id === pid)) { + parentKey = pid; + } else { + // Parent not in set — group under root. + parentKey = rootEventId; + } + if (!childrenOf.has(parentKey)) childrenOf.set(parentKey, []); + childrenOf.get(parentKey).push(evt); + } - // Sort each child group chronologically (oldest first). - for (const arr of childrenOf.values()) { - arr.sort((a, b) => (a.created_at || 0) - (b.created_at || 0)); - } + // Sort each child group chronologically (oldest first). + for (const arr of childrenOf.values()) { + arr.sort((a, b) => (a.created_at || 0) - (b.created_at || 0)); + } - const ordered = []; - const visited = new Set(); + const ordered = []; + const visited = new Set(); - function visit(parentId, level) { - const kids = childrenOf.get(parentId) || []; - for (const child of kids) { - if (visited.has(child.id)) continue; - visited.add(child.id); - const childLevel = levels.get(child.id) ?? level + 1; - ordered.push({ event: child, level: childLevel }); - if (!collapsedIds.has(child.id)) { - visit(child.id, childLevel); - } - } - } + function visit(parentId, level) { + const kids = childrenOf.get(parentId) || []; + for (const child of kids) { + if (visited.has(child.id)) continue; + visited.add(child.id); + const childLevel = levels.get(child.id) ?? level + 1; + ordered.push({ event: child, level: childLevel }); + visit(child.id, childLevel); + } + } - visit(rootEventId, 0); - return ordered; - } + visit(rootEventId, 0); - /* ---------------------------------------------------------------- - Count descendants of a given event id (for the "N replies hidden" - label). Uses the same children map logic. - ---------------------------------------------------------------- */ - function countDescendants(eventId, events) { - const childrenOf = new Map(); - for (const evt of events) { - if (!evt?.id || evt.id === targetEventId) continue; - const pid = getDirectParentId(evt); - const parentKey = pid && events.some((e) => e?.id === pid) ? pid : targetEventId; - if (!childrenOf.has(parentKey)) childrenOf.set(parentKey, []); - childrenOf.get(parentKey).push(evt); - } - let count = 0; - const stack = [eventId]; - const seen = new Set(); - while (stack.length) { - const cur = stack.pop(); - if (seen.has(cur)) continue; - seen.add(cur); - const kids = childrenOf.get(cur) || []; - for (const k of kids) { - count += 1; - stack.push(k.id); - } - } - return count; - } + // Debug check: make sure every event in the input set appears in + // the ordered list. If any are missing (e.g. an orphaned reply + // whose parent is not in the fetched set but was somehow not + // grouped under root), log a warning and append them at the end + // so no reply is silently dropped. + const orderedIds = new Set(ordered.map((o) => o.event?.id)); + const missing = []; + for (const evt of events) { + if (!evt?.id || evt.id === rootEventId) continue; + if (!orderedIds.has(evt.id)) { + missing.push(evt); + } + } + if (missing.length > 0) { + console.warn( + `[post-feed.html] ${missing.length} reply event(s) missing from depth-first order — appending at end:`, + missing.map((e) => e.id) + ); + for (const evt of missing) { + ordered.push({ event: evt, level: levels.get(evt.id) ?? 1 }); + } + } + + return ordered; + } /* ---------------------------------------------------------------- renderReplies() — threaded rendering with vertical indent lines @@ -831,11 +790,9 @@ } const levels = computeReplyLevels(replies, targetEventId); - const ordered = buildDepthFirstOrder(replies, levels, targetEventId, collapsedReplyIds); + const ordered = buildDepthFirstOrder(replies, levels, targetEventId); - const visibleCount = ordered.length; - const hiddenCount = replies.length - visibleCount; - const totalLabel = `${replies.length} ${replies.length === 1 ? 'reply' : 'replies'}` + (hiddenCount > 0 ? ` (${hiddenCount} hidden)` : ''); + const totalLabel = `${replies.length} ${replies.length === 1 ? 'reply' : 'replies'}`; divThreadHeader.textContent = totalLabel; const wiredIds = []; @@ -864,26 +821,6 @@ line.classList.add('selected'); } container.appendChild(line); - - // Click target on the line area — clicking the line for level i - // collapses the subtree of the ancestor at that level. We find - // the ancestor by walking up the parent chain i times. - const hit = document.createElement('div'); - hit.className = 'reply-thread-line-hit'; - hit.style.left = `${i * 12}px`; - hit.title = 'Click to collapse/expand this branch'; - hit.addEventListener('click', (ev) => { - ev.stopPropagation(); - const ancestorId = findAncestorAtLevel(reply.id, i, levels); - if (!ancestorId) return; - if (collapsedReplyIds.has(ancestorId)) { - collapsedReplyIds.delete(ancestorId); - } else { - collapsedReplyIds.add(ancestorId); - } - renderReplies(); - }); - container.appendChild(hit); } // The post card itself. @@ -894,28 +831,6 @@ } container.appendChild(replyEl); - // Collapse toggle button for replies that have children. - const descendantCount = countDescendants(reply.id, replies); - if (descendantCount > 0) { - const toggle = document.createElement('button'); - toggle.className = 'reply-collapse-toggle'; - toggle.type = 'button'; - const collapsed = collapsedReplyIds.has(reply.id); - toggle.textContent = collapsed - ? `▸ ${descendantCount} ${descendantCount === 1 ? 'reply' : 'replies'} hidden` - : `▾ collapse`; - toggle.addEventListener('click', (ev) => { - ev.stopPropagation(); - if (collapsedReplyIds.has(reply.id)) { - collapsedReplyIds.delete(reply.id); - } else { - collapsedReplyIds.add(reply.id); - } - renderReplies(); - }); - container.appendChild(toggle); - } - divReplies.appendChild(container); renderedReplyIds.add(reply.id); wiredIds.push(reply.id); @@ -935,32 +850,6 @@ } } - /* ---------------------------------------------------------------- - findAncestorAtLevel(eventId, targetLevel, levels) - ---------------------------------------------------------------- - Walks up the parent chain to find the ancestor of `eventId` that - sits at `targetLevel`. Used so clicking a vertical line at - position i collapses the ancestor whose subtree that line - represents. - ---------------------------------------------------------------- */ - function findAncestorAtLevel(eventId, targetLevel, levels) { - let cur = eventId; - const byId = new Map(replies.map((r) => [r.id, r])); - if (originalPost) byId.set(originalPost.id, originalPost); - const guard = new Set(); - while (cur && !guard.has(cur)) { - guard.add(cur); - const curLevel = levels.get(cur); - if (curLevel === targetLevel) return cur; - const evt = byId.get(cur); - if (!evt) return null; - const pid = getDirectParentId(evt); - if (!pid) return null; - cur = pid; - } - return null; - } - function appendReplyIfNew(evt) { if (!upsertReply(evt)) return; renderReplies();