feat: add save-for-later button to activitypub reader

Adds a save button to the AP item card action bar that POSTs to
/readlater/save when the readlater plugin is installed. Uses Alpine.js
for optimistic UI update. Button only renders if
application.readlaterEndpoint is set.
This commit is contained in:
Ricardo
2026-02-27 16:02:48 +01:00
parent cfc5af8092
commit 611bd3661c
3 changed files with 40 additions and 1 deletions

View File

@@ -752,6 +752,12 @@
color: var(--color-green50);
}
.ap-card__action--save.ap-card__action--active {
background: #4a9eff22;
border-color: #4a9eff;
color: #4a9eff;
}
.ap-card__action:disabled {
cursor: wait;
opacity: 0.6;

View File

@@ -1,6 +1,6 @@
{
"name": "@rmdes/indiekit-endpoint-activitypub",
"version": "2.0.35",
"version": "2.0.36",
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
"keywords": [
"indiekit",

View File

@@ -151,8 +151,31 @@
x-data="{
liked: {{ 'true' if isLiked else 'false' }},
boosted: {{ 'true' if isBoosted else 'false' }},
saved: false,
loading: false,
error: '',
async saveLater() {
if (this.saved) return;
const el = this.$root;
const itemUrl = el.dataset.itemUrl;
try {
const res = await fetch('/readlater/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: itemUrl,
title: el.closest('article')?.querySelector('p')?.textContent?.substring(0, 80) || itemUrl,
source: 'activitypub'
}),
credentials: 'same-origin'
});
if (res.ok) this.saved = true;
else this.error = 'Failed to save';
} catch (e) {
this.error = e.message;
}
if (this.error) setTimeout(() => this.error = '', 3000);
},
async interact(action) {
if (this.loading) return;
this.loading = true;
@@ -213,6 +236,16 @@
<a href="{{ itemUrl }}" class="ap-card__action ap-card__action--link" target="_blank" rel="noopener">
🔗 {{ __("activitypub.reader.actions.viewOriginal") }}
</a>
{% if application.readlaterEndpoint %}
<button class="ap-card__action ap-card__action--save"
:class="{ 'ap-card__action--active': saved }"
:disabled="saved"
@click="saveLater()"
:title="saved ? 'Saved' : 'Save for later'">
<span x-text="saved ? '🔖' : '📑'"></span>
<span x-text="saved ? 'Saved' : 'Save'"></span>
</button>
{% endif %}
<div x-show="error" x-text="error" class="ap-card__action-error" x-transition></div>
</footer>
{# Close moderation content warning wrapper #}