@rmdes/indiekit-endpoint-activitypub 1.1.9 → 1.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/assets/reader.css
CHANGED
|
@@ -303,6 +303,7 @@
|
|
|
303
303
|
|
|
304
304
|
.ap-card__content .h-card a,
|
|
305
305
|
.ap-card__content a.u-url.mention {
|
|
306
|
+
display: inline;
|
|
306
307
|
color: var(--color-on-offset);
|
|
307
308
|
text-decoration: none;
|
|
308
309
|
white-space: nowrap;
|
|
@@ -321,6 +322,7 @@
|
|
|
321
322
|
|
|
322
323
|
/* Hashtag mentions — keep inline, subtle styling */
|
|
323
324
|
.ap-card__content a.mention.hashtag {
|
|
325
|
+
display: inline;
|
|
324
326
|
color: var(--color-on-offset);
|
|
325
327
|
text-decoration: none;
|
|
326
328
|
white-space: nowrap;
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Temporal } from "@js-temporal/polyfill";
|
|
6
|
-
import { getTimelineItem } from "../storage/timeline.js";
|
|
7
6
|
import { getToken, validateToken } from "../csrf.js";
|
|
8
7
|
import { sanitizeContent } from "../timeline-store.js";
|
|
9
8
|
|
|
@@ -62,7 +61,12 @@ export function composeController(mountPath, plugin) {
|
|
|
62
61
|
};
|
|
63
62
|
|
|
64
63
|
// Try to find the post in our timeline first
|
|
65
|
-
|
|
64
|
+
// Note: Timeline stores uid (canonical AP URL) and url (display URL).
|
|
65
|
+
// The card link passes the display URL, so search both fields.
|
|
66
|
+
const ap_timeline = collections.ap_timeline;
|
|
67
|
+
replyContext = ap_timeline
|
|
68
|
+
? await ap_timeline.findOne({ $or: [{ uid: replyTo }, { url: replyTo }] })
|
|
69
|
+
: null;
|
|
66
70
|
|
|
67
71
|
// If not in timeline, try to look up remotely
|
|
68
72
|
if (!replyContext && plugin._federation) {
|
|
@@ -110,8 +114,11 @@ export function composeController(mountPath, plugin) {
|
|
|
110
114
|
author: { name: authorName, url: authorUrl },
|
|
111
115
|
};
|
|
112
116
|
}
|
|
113
|
-
} catch {
|
|
114
|
-
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.warn(
|
|
119
|
+
`[ActivityPub] lookupObject failed for ${replyTo} (compose):`,
|
|
120
|
+
error.message,
|
|
121
|
+
);
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
124
|
}
|
|
@@ -82,8 +82,11 @@ export function boostController(mountPath, plugin) {
|
|
|
82
82
|
);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
} catch {
|
|
86
|
-
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.warn(
|
|
87
|
+
`[ActivityPub] lookupObject failed for ${url} (boost):`,
|
|
88
|
+
error.message,
|
|
89
|
+
);
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
// Track the interaction
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { validateToken } from "../csrf.js";
|
|
7
|
-
import { getTimelineItem } from "../storage/timeline.js";
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* POST /admin/reader/like — send a Like activity to the post author.
|
|
@@ -61,17 +60,22 @@ export function likeController(mountPath, plugin) {
|
|
|
61
60
|
const author = await remoteObject.getAttributedTo({ documentLoader });
|
|
62
61
|
recipient = Array.isArray(author) ? author[0] : author;
|
|
63
62
|
}
|
|
64
|
-
} catch {
|
|
65
|
-
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.warn(
|
|
65
|
+
`[ActivityPub] lookupObject failed for ${url}:`,
|
|
66
|
+
error.message,
|
|
67
|
+
);
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
// Strategy 2: Use author URL from our timeline (already stored)
|
|
71
|
+
// Note: Timeline items store both uid (canonical AP URL) and url (display URL).
|
|
72
|
+
// The card passes the display URL, so we search by both fields.
|
|
69
73
|
if (!recipient) {
|
|
70
74
|
const { application } = request.app.locals;
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
const ap_timeline = application?.collections?.get("ap_timeline");
|
|
76
|
+
const timelineItem = ap_timeline
|
|
77
|
+
? await ap_timeline.findOne({ $or: [{ uid: url }, { url }] })
|
|
78
|
+
: null;
|
|
75
79
|
const authorUrl = timelineItem?.author?.url;
|
|
76
80
|
|
|
77
81
|
if (authorUrl) {
|
|
@@ -210,15 +214,18 @@ export function unlikeController(mountPath, plugin) {
|
|
|
210
214
|
const author = await remoteObject.getAttributedTo({ documentLoader });
|
|
211
215
|
recipient = Array.isArray(author) ? author[0] : author;
|
|
212
216
|
}
|
|
213
|
-
} catch {
|
|
214
|
-
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.warn(
|
|
219
|
+
`[ActivityPub] lookupObject failed for ${url} (unlike):`,
|
|
220
|
+
error.message,
|
|
221
|
+
);
|
|
215
222
|
}
|
|
216
223
|
|
|
217
224
|
if (!recipient) {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
const ap_timeline = application?.collections?.get("ap_timeline");
|
|
226
|
+
const timelineItem = ap_timeline
|
|
227
|
+
? await ap_timeline.findOne({ $or: [{ uid: url }, { url }] })
|
|
228
|
+
: null;
|
|
222
229
|
const authorUrl = timelineItem?.author?.url;
|
|
223
230
|
|
|
224
231
|
if (authorUrl) {
|
|
@@ -107,26 +107,47 @@ export function readerController(mountPath) {
|
|
|
107
107
|
const unreadCount = await getUnreadNotificationCount(collections);
|
|
108
108
|
|
|
109
109
|
// Get interaction state for liked/boosted indicators
|
|
110
|
+
// Interactions are keyed by canonical AP uid (new) or display url (legacy).
|
|
111
|
+
// Query by both, normalize map keys to uid for template lookup.
|
|
110
112
|
const interactionsCol =
|
|
111
113
|
application?.collections?.get("ap_interactions");
|
|
112
114
|
const interactionMap = {};
|
|
113
115
|
|
|
114
116
|
if (interactionsCol) {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
.filter(Boolean);
|
|
117
|
+
const lookupUrls = new Set();
|
|
118
|
+
const objectUrlToUid = new Map();
|
|
118
119
|
|
|
119
|
-
|
|
120
|
+
for (const item of items) {
|
|
121
|
+
const uid = item.uid;
|
|
122
|
+
const displayUrl = item.url || item.originalUrl;
|
|
123
|
+
|
|
124
|
+
if (uid) {
|
|
125
|
+
lookupUrls.add(uid);
|
|
126
|
+
objectUrlToUid.set(uid, uid);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (displayUrl) {
|
|
130
|
+
lookupUrls.add(displayUrl);
|
|
131
|
+
objectUrlToUid.set(displayUrl, uid || displayUrl);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (lookupUrls.size > 0) {
|
|
120
136
|
const interactions = await interactionsCol
|
|
121
|
-
.find({ objectUrl: { $in:
|
|
137
|
+
.find({ objectUrl: { $in: [...lookupUrls] } })
|
|
122
138
|
.toArray();
|
|
123
139
|
|
|
124
140
|
for (const interaction of interactions) {
|
|
125
|
-
|
|
126
|
-
|
|
141
|
+
// Normalize to uid so template can look up by itemUid
|
|
142
|
+
const key =
|
|
143
|
+
objectUrlToUid.get(interaction.objectUrl) ||
|
|
144
|
+
interaction.objectUrl;
|
|
145
|
+
|
|
146
|
+
if (!interactionMap[key]) {
|
|
147
|
+
interactionMap[key] = {};
|
|
127
148
|
}
|
|
128
149
|
|
|
129
|
-
interactionMap[
|
|
150
|
+
interactionMap[key][interaction.type] = true;
|
|
130
151
|
}
|
|
131
152
|
}
|
|
132
153
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|
|
@@ -97,11 +97,13 @@
|
|
|
97
97
|
{% endif %}
|
|
98
98
|
|
|
99
99
|
{# Interaction buttons — Alpine.js for optimistic updates #}
|
|
100
|
-
{#
|
|
100
|
+
{# Use canonical AP uid for interactions (Fedify lookupObject), display url for links #}
|
|
101
101
|
{% set itemUrl = item.url or item.originalUrl %}
|
|
102
|
-
{% set
|
|
103
|
-
{% set
|
|
102
|
+
{% set itemUid = item.uid or item.url or item.originalUrl %}
|
|
103
|
+
{% set isLiked = interactionMap[itemUid].like if interactionMap[itemUid] else false %}
|
|
104
|
+
{% set isBoosted = interactionMap[itemUid].boost if interactionMap[itemUid] else false %}
|
|
104
105
|
<footer class="ap-card__actions"
|
|
106
|
+
data-item-uid="{{ itemUid }}"
|
|
105
107
|
data-item-url="{{ itemUrl }}"
|
|
106
108
|
data-csrf-token="{{ csrfToken }}"
|
|
107
109
|
data-mount-path="{{ mountPath }}"
|
|
@@ -115,7 +117,7 @@
|
|
|
115
117
|
this.loading = true;
|
|
116
118
|
this.error = '';
|
|
117
119
|
const el = this.$root;
|
|
118
|
-
const
|
|
120
|
+
const itemUid = el.dataset.itemUid;
|
|
119
121
|
const csrfToken = el.dataset.csrfToken;
|
|
120
122
|
const basePath = el.dataset.mountPath;
|
|
121
123
|
const prev = { liked: this.liked, boosted: this.boosted };
|
|
@@ -130,7 +132,7 @@
|
|
|
130
132
|
'Content-Type': 'application/json',
|
|
131
133
|
'X-CSRF-Token': csrfToken
|
|
132
134
|
},
|
|
133
|
-
body: JSON.stringify({ url:
|
|
135
|
+
body: JSON.stringify({ url: itemUid })
|
|
134
136
|
});
|
|
135
137
|
const data = await res.json();
|
|
136
138
|
if (!data.success) {
|
|
@@ -147,7 +149,7 @@
|
|
|
147
149
|
if (this.error) setTimeout(() => this.error = '', 3000);
|
|
148
150
|
}
|
|
149
151
|
}">
|
|
150
|
-
<a href="{{ mountPath }}/admin/reader/compose?replyTo={{
|
|
152
|
+
<a href="{{ mountPath }}/admin/reader/compose?replyTo={{ itemUid | urlencode }}"
|
|
151
153
|
class="ap-card__action ap-card__action--reply"
|
|
152
154
|
title="{{ __('activitypub.reader.actions.reply') }}">
|
|
153
155
|
↩ {{ __("activitypub.reader.actions.reply") }}
|