diff --git a/lib/controllers/reader.js b/lib/controllers/reader.js index 537a413..a83175b 100644 --- a/lib/controllers/reader.js +++ b/lib/controllers/reader.js @@ -1184,10 +1184,10 @@ export async function timeline(request, response) { // Get channels with colors for filtering UI and item decoration const channelList = await getChannelsWithColors(application, userId); - // Build channel lookup map (ObjectId string -> { name, color }) + // Build channel lookup map (ObjectId string -> { name, color, uid }) const channelMap = new Map(); for (const ch of channelList) { - channelMap.set(ch._id.toString(), { name: ch.name, color: ch.color }); + channelMap.set(ch._id.toString(), { name: ch.name, color: ch.color, uid: ch.uid }); } // Parse excluded channel IDs from query params @@ -1223,6 +1223,7 @@ export async function timeline(request, response) { if (info) { item._channelName = info.name; item._channelColor = info.color; + item._channelUid = info.uid; } } } diff --git a/views/partials/item-card.njk b/views/partials/item-card.njk index 77a81e6..a8e98f1 100644 --- a/views/partials/item-card.njk +++ b/views/partials/item-card.njk @@ -202,6 +202,7 @@ class="item-actions__button item-actions__mark-read" data-action="mark-read" data-item-id="{{ item._id }}" + {% if item._channelUid %}data-channel-uid="{{ item._channelUid }}"{% endif %} title="Mark as read"> {{ icon("checkboxChecked") }} Mark read diff --git a/views/timeline.njk b/views/timeline.njk index 9ae0457..c32cb7f 100644 --- a/views/timeline.njk +++ b/views/timeline.njk @@ -95,6 +95,61 @@ break; } }); + + // Handle individual mark-read buttons + const microsubApiUrl = '{{ baseUrl }}'.replace(/\/reader$/, ''); + + timeline.addEventListener('click', async (e) => { + const button = e.target.closest('.item-actions__mark-read'); + if (!button) return; + + e.preventDefault(); + e.stopPropagation(); + + const itemId = button.dataset.itemId; + const channelUid = button.dataset.channelUid; + if (!itemId || !channelUid) return; + + button.disabled = true; + + try { + const formData = new URLSearchParams(); + formData.append('action', 'timeline'); + formData.append('method', 'mark_read'); + formData.append('channel', channelUid); + formData.append('entry', itemId); + + const response = await fetch(microsubApiUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: formData.toString(), + credentials: 'same-origin' + }); + + if (response.ok) { + const card = button.closest('.item-card'); + if (card) { + card.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; + card.style.opacity = '0'; + card.style.transform = 'translateX(-20px)'; + setTimeout(() => { + const wrapper = card.closest('.timeline-view__item'); + if (wrapper) wrapper.remove(); + else card.remove(); + if (timeline.querySelectorAll('.item-card').length === 0) { + location.reload(); + } + }, 300); + } + } else { + console.error('Failed to mark item as read'); + button.disabled = false; + } + } catch (error) { + console.error('Error marking item as read:', error); + button.disabled = false; + } + }); } {% endblock %}