feat: add bookmark to timeline, mark-read to item view

- Add bookmark button to item-card.njk (timeline view)
- Add mark-read button to item.njk (full item view)
- Add JavaScript handler for mark-read on item page
- Pass channel info to item template for mark-read API call

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ricardo
2026-02-06 22:56:03 +01:00
parent 6b19ac4cc7
commit 59cbe7c238
4 changed files with 68 additions and 1 deletions

View File

@@ -314,9 +314,17 @@ export async function item(request, response) {
return response.status(404).render("404"); return response.status(404).render("404");
} }
// Get the channel for this item (needed for mark-read)
let channel = null;
if (itemDocument.channelId) {
const channelsCollection = application.collections.get("microsub_channels");
channel = await channelsCollection.findOne({ _id: itemDocument.channelId });
}
response.render("item", { response.render("item", {
title: itemDocument.name || "Item", title: itemDocument.name || "Item",
item: itemDocument, item: itemDocument,
channel,
baseUrl: request.baseUrl, baseUrl: request.baseUrl,
}); });
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@rmdes/indiekit-endpoint-microsub", "name": "@rmdes/indiekit-endpoint-microsub",
"version": "1.0.18", "version": "1.0.19",
"description": "Microsub endpoint for Indiekit. Enables subscribing to feeds and reading content using the Microsub protocol.", "description": "Microsub endpoint for Indiekit. Enables subscribing to feeds and reading content using the Microsub protocol.",
"keywords": [ "keywords": [
"indiekit", "indiekit",

View File

@@ -146,6 +146,61 @@
<a href="{{ baseUrl }}/compose?bookmark={{ item.url | urlencode }}" class="button button--secondary button--small"> <a href="{{ baseUrl }}/compose?bookmark={{ item.url | urlencode }}" class="button button--secondary button--small">
{{ icon("bookmark") }} {{ __("microsub.item.bookmark") }} {{ icon("bookmark") }} {{ __("microsub.item.bookmark") }}
</a> </a>
{% if not item._is_read %}
<button type="button"
class="button button--secondary button--small item__mark-read"
data-item-id="{{ item._id }}"
data-channel="{{ channel.uid }}">
{{ icon("checkboxChecked") }} {{ __("microsub.timeline.markRead") }}
</button>
{% endif %}
</footer> </footer>
</article> </article>
<script type="module">
// Handle mark-read button
const markReadBtn = document.querySelector('.item__mark-read');
if (markReadBtn) {
markReadBtn.addEventListener('click', async () => {
const itemId = markReadBtn.dataset.itemId;
const channelUid = markReadBtn.dataset.channel;
const microsubApiUrl = '{{ baseUrl }}'.replace(/\/reader.*$/, '');
markReadBtn.disabled = true;
markReadBtn.textContent = 'Marking...';
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) {
markReadBtn.textContent = 'Marked as read';
markReadBtn.classList.add('button--success');
setTimeout(() => {
markReadBtn.remove();
}, 1500);
} else {
markReadBtn.textContent = 'Failed';
markReadBtn.disabled = false;
}
} catch (error) {
console.error('Error:', error);
markReadBtn.textContent = 'Error';
markReadBtn.disabled = false;
}
});
}
</script>
{% endblock %} {% endblock %}

View File

@@ -165,6 +165,10 @@
{{ icon("repost") }} {{ icon("repost") }}
<span class="visually-hidden">Repost</span> <span class="visually-hidden">Repost</span>
</a> </a>
<a href="{{ baseUrl }}/compose?bookmark={{ item.url | urlencode }}" class="item-actions__button" title="Bookmark">
{{ icon("bookmark") }}
<span class="visually-hidden">Bookmark</span>
</a>
{% if not item._is_read %} {% if not item._is_read %}
<button type="button" <button type="button"
class="item-actions__button item-actions__mark-read" class="item-actions__button item-actions__mark-read"