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:
@@ -752,6 +752,12 @@
|
|||||||
color: var(--color-green50);
|
color: var(--color-green50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ap-card__action--save.ap-card__action--active {
|
||||||
|
background: #4a9eff22;
|
||||||
|
border-color: #4a9eff;
|
||||||
|
color: #4a9eff;
|
||||||
|
}
|
||||||
|
|
||||||
.ap-card__action:disabled {
|
.ap-card__action:disabled {
|
||||||
cursor: wait;
|
cursor: wait;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
"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.",
|
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"indiekit",
|
"indiekit",
|
||||||
|
|||||||
@@ -151,8 +151,31 @@
|
|||||||
x-data="{
|
x-data="{
|
||||||
liked: {{ 'true' if isLiked else 'false' }},
|
liked: {{ 'true' if isLiked else 'false' }},
|
||||||
boosted: {{ 'true' if isBoosted else 'false' }},
|
boosted: {{ 'true' if isBoosted else 'false' }},
|
||||||
|
saved: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: '',
|
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) {
|
async interact(action) {
|
||||||
if (this.loading) return;
|
if (this.loading) return;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -213,6 +236,16 @@
|
|||||||
<a href="{{ itemUrl }}" class="ap-card__action ap-card__action--link" target="_blank" rel="noopener">
|
<a href="{{ itemUrl }}" class="ap-card__action ap-card__action--link" target="_blank" rel="noopener">
|
||||||
🔗 {{ __("activitypub.reader.actions.viewOriginal") }}
|
🔗 {{ __("activitypub.reader.actions.viewOriginal") }}
|
||||||
</a>
|
</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>
|
<div x-show="error" x-text="error" class="ap-card__action-error" x-transition></div>
|
||||||
</footer>
|
</footer>
|
||||||
{# Close moderation content warning wrapper #}
|
{# Close moderation content warning wrapper #}
|
||||||
|
|||||||
Reference in New Issue
Block a user