@rmdes/indiekit-endpoint-activitypub 1.0.27 → 1.0.29
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/index.js
CHANGED
|
@@ -149,11 +149,11 @@ export default class ActivityPubEndpoint {
|
|
|
149
149
|
router.get("/admin/following", followingController(mp));
|
|
150
150
|
router.get("/admin/activities", activitiesController(mp));
|
|
151
151
|
router.get("/admin/featured", featuredGetController(mp));
|
|
152
|
-
router.post("/admin/featured/pin", featuredPinController());
|
|
153
|
-
router.post("/admin/featured/unpin", featuredUnpinController());
|
|
152
|
+
router.post("/admin/featured/pin", featuredPinController(mp));
|
|
153
|
+
router.post("/admin/featured/unpin", featuredUnpinController(mp));
|
|
154
154
|
router.get("/admin/tags", featuredTagsGetController(mp));
|
|
155
|
-
router.post("/admin/tags/add", featuredTagsAddController());
|
|
156
|
-
router.post("/admin/tags/remove", featuredTagsRemoveController());
|
|
155
|
+
router.post("/admin/tags/add", featuredTagsAddController(mp));
|
|
156
|
+
router.post("/admin/tags/remove", featuredTagsRemoveController(mp));
|
|
157
157
|
router.get("/admin/profile", profileGetController(mp));
|
|
158
158
|
router.post("/admin/profile", profilePostController(mp));
|
|
159
159
|
router.get("/admin/migrate", migrateGetController(mp, this.options));
|
|
@@ -24,7 +24,7 @@ export function featuredTagsGetController(mountPath) {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export function featuredTagsAddController() {
|
|
27
|
+
export function featuredTagsAddController(mountPath) {
|
|
28
28
|
return async (request, response, next) => {
|
|
29
29
|
try {
|
|
30
30
|
const { application } = request.app.locals;
|
|
@@ -44,14 +44,14 @@ export function featuredTagsAddController() {
|
|
|
44
44
|
{ upsert: true },
|
|
45
45
|
);
|
|
46
46
|
|
|
47
|
-
response.redirect(
|
|
47
|
+
response.redirect(`${mountPath}/admin/tags`);
|
|
48
48
|
} catch (error) {
|
|
49
49
|
next(error);
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
export function featuredTagsRemoveController() {
|
|
54
|
+
export function featuredTagsRemoveController(mountPath) {
|
|
55
55
|
return async (request, response, next) => {
|
|
56
56
|
try {
|
|
57
57
|
const { application } = request.app.locals;
|
|
@@ -63,7 +63,7 @@ export function featuredTagsRemoveController() {
|
|
|
63
63
|
|
|
64
64
|
await collection.deleteOne({ tag });
|
|
65
65
|
|
|
66
|
-
response.redirect(
|
|
66
|
+
response.redirect(`${mountPath}/admin/tags`);
|
|
67
67
|
} catch (error) {
|
|
68
68
|
next(error);
|
|
69
69
|
}
|
|
@@ -69,7 +69,7 @@ export function featuredGetController(mountPath) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export function featuredPinController() {
|
|
72
|
+
export function featuredPinController(mountPath) {
|
|
73
73
|
return async (request, response, next) => {
|
|
74
74
|
try {
|
|
75
75
|
const { application } = request.app.locals;
|
|
@@ -90,14 +90,14 @@ export function featuredPinController() {
|
|
|
90
90
|
{ upsert: true },
|
|
91
91
|
);
|
|
92
92
|
|
|
93
|
-
response.redirect(
|
|
93
|
+
response.redirect(`${mountPath}/admin/featured`);
|
|
94
94
|
} catch (error) {
|
|
95
95
|
next(error);
|
|
96
96
|
}
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
export function featuredUnpinController() {
|
|
100
|
+
export function featuredUnpinController(mountPath) {
|
|
101
101
|
return async (request, response, next) => {
|
|
102
102
|
try {
|
|
103
103
|
const { application } = request.app.locals;
|
|
@@ -109,7 +109,7 @@ export function featuredUnpinController() {
|
|
|
109
109
|
|
|
110
110
|
await collection.deleteOne({ postUrl });
|
|
111
111
|
|
|
112
|
-
response.redirect(
|
|
112
|
+
response.redirect(`${mountPath}/admin/featured`);
|
|
113
113
|
} catch (error) {
|
|
114
114
|
next(error);
|
|
115
115
|
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* POST: saves updated profile fields back to ap_profile
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
const ACTOR_TYPES = ["Person", "Service", "Organization"];
|
|
9
|
+
|
|
8
10
|
export function profileGetController(mountPath) {
|
|
9
11
|
return async (request, response, next) => {
|
|
10
12
|
try {
|
|
@@ -18,6 +20,7 @@ export function profileGetController(mountPath) {
|
|
|
18
20
|
title: response.locals.__("activitypub.profile.title"),
|
|
19
21
|
mountPath,
|
|
20
22
|
profile,
|
|
23
|
+
actorTypes: ACTOR_TYPES,
|
|
21
24
|
result: null,
|
|
22
25
|
});
|
|
23
26
|
} catch (error) {
|
|
@@ -42,10 +45,23 @@ export function profilePostController(mountPath) {
|
|
|
42
45
|
url,
|
|
43
46
|
icon,
|
|
44
47
|
image,
|
|
48
|
+
actorType,
|
|
45
49
|
manuallyApprovesFollowers,
|
|
46
50
|
authorizedFetch,
|
|
47
51
|
} = request.body;
|
|
48
52
|
|
|
53
|
+
// Parse profile links (attachments) from form arrays
|
|
54
|
+
const linkNames = [].concat(request.body["link_name[]"] || []);
|
|
55
|
+
const linkValues = [].concat(request.body["link_value[]"] || []);
|
|
56
|
+
const attachments = [];
|
|
57
|
+
for (let i = 0; i < linkNames.length; i++) {
|
|
58
|
+
const n = linkNames[i]?.trim();
|
|
59
|
+
const v = linkValues[i]?.trim();
|
|
60
|
+
if (n && v) {
|
|
61
|
+
attachments.push({ name: n, value: v });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
const update = {
|
|
50
66
|
$set: {
|
|
51
67
|
name: name?.trim() || "",
|
|
@@ -53,8 +69,10 @@ export function profilePostController(mountPath) {
|
|
|
53
69
|
url: url?.trim() || "",
|
|
54
70
|
icon: icon?.trim() || "",
|
|
55
71
|
image: image?.trim() || "",
|
|
72
|
+
actorType: ACTOR_TYPES.includes(actorType) ? actorType : "Person",
|
|
56
73
|
manuallyApprovesFollowers: manuallyApprovesFollowers === "true",
|
|
57
74
|
authorizedFetch: authorizedFetch === "true",
|
|
75
|
+
attachments,
|
|
58
76
|
updatedAt: new Date().toISOString(),
|
|
59
77
|
},
|
|
60
78
|
};
|
|
@@ -67,6 +85,7 @@ export function profilePostController(mountPath) {
|
|
|
67
85
|
title: response.locals.__("activitypub.profile.title"),
|
|
68
86
|
mountPath,
|
|
69
87
|
profile,
|
|
88
|
+
actorTypes: ACTOR_TYPES,
|
|
70
89
|
result: {
|
|
71
90
|
type: "success",
|
|
72
91
|
text: response.locals.__("activitypub.profile.saved"),
|
package/lib/federation-setup.js
CHANGED
|
@@ -197,7 +197,11 @@ export function setupFederation(options) {
|
|
|
197
197
|
personOptions.published = Temporal.Instant.from(profile.createdAt);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
// Actor type from profile overrides config default
|
|
201
|
+
const profileActorType = profile.actorType || actorType;
|
|
202
|
+
const ResolvedActorClass = actorTypeMap[profileActorType] || ActorClass;
|
|
203
|
+
|
|
204
|
+
return new ResolvedActorClass(personOptions);
|
|
201
205
|
},
|
|
202
206
|
)
|
|
203
207
|
.mapHandle((_ctx, username) => {
|
package/locales/en.json
CHANGED
|
@@ -38,6 +38,14 @@
|
|
|
38
38
|
"imageHint": "URL to a banner image shown at the top of your profile",
|
|
39
39
|
"manualApprovalLabel": "Manually approve followers",
|
|
40
40
|
"manualApprovalHint": "When enabled, follow requests require your approval before they take effect",
|
|
41
|
+
"actorTypeLabel": "Actor type",
|
|
42
|
+
"actorTypeHint": "How your account appears in the fediverse. Person for individuals, Service for bots or automated accounts, Organization for groups or companies.",
|
|
43
|
+
"linksLabel": "Profile links",
|
|
44
|
+
"linksHint": "Links shown on your fediverse profile. Add your website, social accounts, or other URLs. Pages that link back with rel=\"me\" will show as verified on Mastodon.",
|
|
45
|
+
"linkNameLabel": "Label",
|
|
46
|
+
"linkValueLabel": "URL",
|
|
47
|
+
"addLink": "Add link",
|
|
48
|
+
"removeLink": "Remove",
|
|
41
49
|
"authorizedFetchLabel": "Require authorized fetch (secure mode)",
|
|
42
50
|
"authorizedFetchHint": "When enabled, only servers with valid HTTP Signatures can fetch your actor and collections. This improves privacy but may reduce compatibility with some clients.",
|
|
43
51
|
"save": "Save profile",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.29",
|
|
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",
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
{% from "input/macro.njk" import input with context %}
|
|
5
5
|
{% from "textarea/macro.njk" import textarea with context %}
|
|
6
6
|
{% from "checkboxes/macro.njk" import checkboxes with context %}
|
|
7
|
+
{% from "radios/macro.njk" import radios with context %}
|
|
7
8
|
{% from "button/macro.njk" import button with context %}
|
|
8
9
|
{% from "notification-banner/macro.njk" import notificationBanner with context %}
|
|
9
10
|
{% from "prose/macro.njk" import prose with context %}
|
|
@@ -57,6 +58,50 @@
|
|
|
57
58
|
type: "url"
|
|
58
59
|
}) }}
|
|
59
60
|
|
|
61
|
+
{{ radios({
|
|
62
|
+
name: "actorType",
|
|
63
|
+
fieldset: {
|
|
64
|
+
legend: __("activitypub.profile.actorTypeLabel")
|
|
65
|
+
},
|
|
66
|
+
hint: __("activitypub.profile.actorTypeHint"),
|
|
67
|
+
items: [{
|
|
68
|
+
label: "Person",
|
|
69
|
+
value: "Person"
|
|
70
|
+
}, {
|
|
71
|
+
label: "Service",
|
|
72
|
+
value: "Service"
|
|
73
|
+
}, {
|
|
74
|
+
label: "Organization",
|
|
75
|
+
value: "Organization"
|
|
76
|
+
}],
|
|
77
|
+
values: [profile.actorType or "Person"]
|
|
78
|
+
}) }}
|
|
79
|
+
|
|
80
|
+
<fieldset class="fieldset" style="margin-block-end: var(--space-l);">
|
|
81
|
+
<legend class="label">{{ __("activitypub.profile.linksLabel") }}</legend>
|
|
82
|
+
<p class="hint">{{ __("activitypub.profile.linksHint") }}</p>
|
|
83
|
+
|
|
84
|
+
<div id="profile-links">
|
|
85
|
+
{% if profile.attachments and profile.attachments.length > 0 %}
|
|
86
|
+
{% for att in profile.attachments %}
|
|
87
|
+
<div class="profile-link-row" style="display: grid; grid-template-columns: 1fr 2fr auto; gap: var(--space-s); align-items: end; margin-block-end: var(--space-s);">
|
|
88
|
+
<div>
|
|
89
|
+
<label class="label" for="link_name_{{ loop.index }}">{{ __("activitypub.profile.linkNameLabel") }}</label>
|
|
90
|
+
<input class="input" type="text" id="link_name_{{ loop.index }}" name="link_name[]" value="{{ att.name }}" placeholder="Website">
|
|
91
|
+
</div>
|
|
92
|
+
<div>
|
|
93
|
+
<label class="label" for="link_value_{{ loop.index }}">{{ __("activitypub.profile.linkValueLabel") }}</label>
|
|
94
|
+
<input class="input" type="url" id="link_value_{{ loop.index }}" name="link_value[]" value="{{ att.value }}" placeholder="https://example.com">
|
|
95
|
+
</div>
|
|
96
|
+
<button type="button" class="button button--small" onclick="this.closest('.profile-link-row').remove()" style="margin-block-end: 4px;">{{ __("activitypub.profile.removeLink") }}</button>
|
|
97
|
+
</div>
|
|
98
|
+
{% endfor %}
|
|
99
|
+
{% endif %}
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<button type="button" class="button button--small" id="add-link-btn">{{ __("activitypub.profile.addLink") }}</button>
|
|
103
|
+
</fieldset>
|
|
104
|
+
|
|
60
105
|
{{ checkboxes({
|
|
61
106
|
name: "manuallyApprovesFollowers",
|
|
62
107
|
items: [
|
|
@@ -83,4 +128,57 @@
|
|
|
83
128
|
|
|
84
129
|
{{ button({ text: __("activitypub.profile.save") }) }}
|
|
85
130
|
</form>
|
|
131
|
+
|
|
132
|
+
<script>
|
|
133
|
+
(function() {
|
|
134
|
+
var linkCount = {{ (profile.attachments.length if profile.attachments) or 0 }};
|
|
135
|
+
document.getElementById('add-link-btn').addEventListener('click', function() {
|
|
136
|
+
linkCount++;
|
|
137
|
+
var container = document.getElementById('profile-links');
|
|
138
|
+
var row = document.createElement('div');
|
|
139
|
+
row.className = 'profile-link-row';
|
|
140
|
+
row.style.cssText = 'display: grid; grid-template-columns: 1fr 2fr auto; gap: var(--space-s); align-items: end; margin-block-end: var(--space-s);';
|
|
141
|
+
|
|
142
|
+
var nameDiv = document.createElement('div');
|
|
143
|
+
var nameLabel = document.createElement('label');
|
|
144
|
+
nameLabel.className = 'label';
|
|
145
|
+
nameLabel.setAttribute('for', 'link_name_' + linkCount);
|
|
146
|
+
nameLabel.textContent = 'Label';
|
|
147
|
+
var nameInput = document.createElement('input');
|
|
148
|
+
nameInput.className = 'input';
|
|
149
|
+
nameInput.type = 'text';
|
|
150
|
+
nameInput.id = 'link_name_' + linkCount;
|
|
151
|
+
nameInput.name = 'link_name[]';
|
|
152
|
+
nameInput.placeholder = 'Website';
|
|
153
|
+
nameDiv.appendChild(nameLabel);
|
|
154
|
+
nameDiv.appendChild(nameInput);
|
|
155
|
+
|
|
156
|
+
var valueDiv = document.createElement('div');
|
|
157
|
+
var valueLabel = document.createElement('label');
|
|
158
|
+
valueLabel.className = 'label';
|
|
159
|
+
valueLabel.setAttribute('for', 'link_value_' + linkCount);
|
|
160
|
+
valueLabel.textContent = 'URL';
|
|
161
|
+
var valueInput = document.createElement('input');
|
|
162
|
+
valueInput.className = 'input';
|
|
163
|
+
valueInput.type = 'url';
|
|
164
|
+
valueInput.id = 'link_value_' + linkCount;
|
|
165
|
+
valueInput.name = 'link_value[]';
|
|
166
|
+
valueInput.placeholder = 'https://example.com';
|
|
167
|
+
valueDiv.appendChild(valueLabel);
|
|
168
|
+
valueDiv.appendChild(valueInput);
|
|
169
|
+
|
|
170
|
+
var removeBtn = document.createElement('button');
|
|
171
|
+
removeBtn.type = 'button';
|
|
172
|
+
removeBtn.className = 'button button--small';
|
|
173
|
+
removeBtn.style.cssText = 'margin-block-end: 4px;';
|
|
174
|
+
removeBtn.textContent = 'Remove';
|
|
175
|
+
removeBtn.addEventListener('click', function() { row.remove(); });
|
|
176
|
+
|
|
177
|
+
row.appendChild(nameDiv);
|
|
178
|
+
row.appendChild(valueDiv);
|
|
179
|
+
row.appendChild(removeBtn);
|
|
180
|
+
container.appendChild(row);
|
|
181
|
+
});
|
|
182
|
+
})();
|
|
183
|
+
</script>
|
|
86
184
|
{% endblock %}
|