From d1f0fffe35af358faef243c0303b43477b137cce Mon Sep 17 00:00:00 2001 From: Ricardo Date: Thu, 5 Mar 2026 20:25:30 +0100 Subject: [PATCH] fix: mark-as-read for items from orphan channels Items from channels with userId: null (created during earlier setup) appeared in the unified timeline but had no _channelUid, causing the mark-read JS handler to silently abort. Fall back to channelId (MongoDB ObjectId) when channelUid is unavailable, and resolve it server-side via getChannelById. Confab-Link: http://localhost:8080/sessions/4d40ef89-a713-48c1-b4ed-0ffafca25677 --- lib/controllers/timeline.js | 14 +++++++++++--- views/partials/item-card.njk | 1 + views/timeline.njk | 5 +++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/controllers/timeline.js b/lib/controllers/timeline.js index d22fa52..be13143 100644 --- a/lib/controllers/timeline.js +++ b/lib/controllers/timeline.js @@ -6,7 +6,7 @@ import { IndiekitError } from "@indiekit/error"; import { proxyItemImages } from "../media/proxy.js"; -import { getChannel } from "../storage/channels.js"; +import { getChannel, getChannelById } from "../storage/channels.js"; import { getTimelineItems, markItemsRead, @@ -72,8 +72,16 @@ export async function action(request, response) { validateChannel(channel); - // Verify channel exists - const channelDocument = await getChannel(application, channel, userId); + // Verify channel exists — try by UID first, fall back to ObjectId + // (timeline view may send ObjectId string for items from orphan channels) + let channelDocument = await getChannel(application, channel, userId); + if (!channelDocument) { + try { + channelDocument = await getChannelById(application, channel); + } catch { + // Invalid ObjectId format — channel string is not a valid ObjectId + } + } if (!channelDocument) { throw new IndiekitError("Channel not found", { status: 404, diff --git a/views/partials/item-card.njk b/views/partials/item-card.njk index f21c1bd..f5ae360 100644 --- a/views/partials/item-card.njk +++ b/views/partials/item-card.njk @@ -203,6 +203,7 @@ data-action="mark-read" data-item-id="{{ item._id }}" {% if item._channelUid %}data-channel-uid="{{ item._channelUid }}"{% endif %} + {% if item._channelId %}data-channel-id="{{ item._channelId }}"{% endif %} title="Mark as read"> {{ icon("checkboxChecked") }} Mark read diff --git a/views/timeline.njk b/views/timeline.njk index 4b0a83a..2fc4cf7 100644 --- a/views/timeline.njk +++ b/views/timeline.njk @@ -108,7 +108,8 @@ const itemId = button.dataset.itemId; const channelUid = button.dataset.channelUid; - if (!itemId || !channelUid) return; + const channelId = button.dataset.channelId; + if (!itemId || (!channelUid && !channelId)) return; button.disabled = true; @@ -116,7 +117,7 @@ const formData = new URLSearchParams(); formData.append('action', 'timeline'); formData.append('method', 'mark_read'); - formData.append('channel', channelUid); + formData.append('channel', channelUid || channelId); formData.append('entry', itemId); const response = await fetch(microsubApiUrl, {