@rmdes/indiekit-endpoint-activitypub 2.6.1 → 2.7.0

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.
@@ -16,7 +16,7 @@
16
16
  display: flex;
17
17
  overflow: hidden;
18
18
  border-radius: 8px;
19
- border: 1px solid var(--color-neutral-lighter);
19
+ border: 1px solid var(--color-outline);
20
20
  background-color: var(--color-offset);
21
21
  text-decoration: none;
22
22
  color: inherit;
@@ -105,7 +105,7 @@
105
105
 
106
106
  .ap-post-detail__back-link {
107
107
  font-size: 0.875rem;
108
- color: var(--color-primary);
108
+ color: var(--color-primary-on-background);
109
109
  text-decoration: none;
110
110
  }
111
111
 
@@ -121,7 +121,7 @@
121
121
  letter-spacing: 0.05em;
122
122
  margin: var(--space-l) 0 var(--space-s);
123
123
  padding-bottom: var(--space-xs);
124
- border-bottom: 1px solid var(--color-neutral-lighter);
124
+ border-bottom: 1px solid var(--color-outline);
125
125
  }
126
126
 
127
127
  .ap-post-detail__main {
@@ -142,7 +142,7 @@
142
142
  .ap-post-detail__parents .ap-post-detail__parent-item {
143
143
  position: relative;
144
144
  padding-left: var(--space-m);
145
- border-left: 2px solid var(--color-neutral-lighter);
145
+ border-left: 2px solid var(--color-outline);
146
146
  }
147
147
 
148
148
  /* Main post highlight */
package/assets/reader.css CHANGED
@@ -18,7 +18,7 @@
18
18
  }
19
19
 
20
20
  .ap-breadcrumb a {
21
- color: var(--color-accent);
21
+ color: var(--color-primary-on-background);
22
22
  text-decoration: none;
23
23
  }
24
24
 
@@ -115,7 +115,7 @@
115
115
 
116
116
  .ap-tab--active {
117
117
  border-bottom-color: var(--color-primary);
118
- color: var(--color-primary);
118
+ color: var(--color-primary-on-background);
119
119
  font-weight: 600;
120
120
  }
121
121
 
@@ -242,7 +242,7 @@
242
242
  }
243
243
 
244
244
  .ap-card__reply-to a {
245
- color: var(--color-primary);
245
+ color: var(--color-primary-on-background);
246
246
  text-decoration: none;
247
247
  }
248
248
 
@@ -349,7 +349,7 @@
349
349
 
350
350
  .ap-card__timestamp-link:hover {
351
351
  text-decoration: underline;
352
- color: var(--color-primary);
352
+ color: var(--color-primary-on-background);
353
353
  }
354
354
 
355
355
  /* ==========================================================================
@@ -385,7 +385,7 @@
385
385
  }
386
386
 
387
387
  .ap-card__content a {
388
- color: var(--color-primary);
388
+ color: var(--color-primary-on-background);
389
389
  }
390
390
 
391
391
  .ap-card__content p {
@@ -447,7 +447,7 @@
447
447
 
448
448
  .ap-card__content .h-card a:hover,
449
449
  .ap-card__content a.u-url.mention:hover {
450
- color: var(--color-primary);
450
+ color: var(--color-primary-on-background);
451
451
  text-decoration: underline;
452
452
  }
453
453
 
@@ -464,7 +464,7 @@
464
464
  }
465
465
 
466
466
  .ap-card__content a.mention.hashtag:hover {
467
- color: var(--color-primary);
467
+ color: var(--color-primary-on-background);
468
468
  text-decoration: underline;
469
469
  }
470
470
 
@@ -720,17 +720,17 @@
720
720
  }
721
721
 
722
722
  .ap-card__mention {
723
- background: color-mix(in srgb, var(--color-accent) 12%, transparent);
723
+ background: color-mix(in srgb, var(--color-primary) 12%, transparent);
724
724
  border-radius: var(--border-radius-large);
725
- color: var(--color-accent);
725
+ color: var(--color-primary-on-background);
726
726
  font-size: var(--font-size-s);
727
727
  padding: 2px var(--space-xs);
728
728
  text-decoration: none;
729
729
  }
730
730
 
731
731
  .ap-card__mention:hover {
732
- background: color-mix(in srgb, var(--color-accent) 22%, transparent);
733
- color: var(--color-accent);
732
+ background: color-mix(in srgb, var(--color-primary) 22%, transparent);
733
+ color: var(--color-primary-on-background);
734
734
  }
735
735
 
736
736
  .ap-card__mention--legacy {
@@ -874,7 +874,7 @@
874
874
  }
875
875
 
876
876
  .ap-pagination a {
877
- color: var(--color-primary);
877
+ color: var(--color-primary-on-background);
878
878
  text-decoration: none;
879
879
  }
880
880
 
@@ -976,34 +976,6 @@
976
976
  gap: var(--space-m);
977
977
  }
978
978
 
979
- .ap-compose__mode {
980
- border: var(--border-width-thin) solid var(--color-outline);
981
- border-radius: var(--border-radius-small);
982
- display: flex;
983
- flex-direction: column;
984
- gap: var(--space-s);
985
- padding: var(--space-m);
986
- }
987
-
988
- .ap-compose__mode legend {
989
- font-weight: 600;
990
- }
991
-
992
- .ap-compose__mode-option {
993
- cursor: pointer;
994
- display: flex;
995
- flex-wrap: wrap;
996
- gap: var(--space-xs);
997
- }
998
-
999
- .ap-compose__mode-hint {
1000
- color: var(--color-on-offset);
1001
- display: block;
1002
- font-size: var(--font-size-s);
1003
- margin-left: 1.5em;
1004
- width: 100%;
1005
- }
1006
-
1007
979
  .ap-compose__editor {
1008
980
  position: relative;
1009
981
  }
@@ -1027,21 +999,6 @@
1027
999
  outline-offset: -2px;
1028
1000
  }
1029
1001
 
1030
- .ap-compose__counter {
1031
- font-size: var(--font-size-s);
1032
- padding-top: var(--space-xs);
1033
- text-align: right;
1034
- }
1035
-
1036
- .ap-compose__counter--warn {
1037
- color: var(--color-yellow50);
1038
- }
1039
-
1040
- .ap-compose__counter--over {
1041
- color: var(--color-error);
1042
- font-weight: 600;
1043
- }
1044
-
1045
1002
  .ap-compose__syndication {
1046
1003
  border: var(--border-width-thin) solid var(--color-outline);
1047
1004
  border-radius: var(--border-radius-small);
@@ -1120,11 +1077,11 @@
1120
1077
  }
1121
1078
 
1122
1079
  .ap-notifications__btn--danger {
1123
- color: var(--color-red45);
1080
+ color: var(--color-error);
1124
1081
  }
1125
1082
 
1126
1083
  .ap-notifications__btn--danger:hover {
1127
- border-color: var(--color-red45);
1084
+ border-color: var(--color-error);
1128
1085
  }
1129
1086
 
1130
1087
  .ap-notification {
@@ -1230,7 +1187,7 @@
1230
1187
 
1231
1188
  .ap-notification__dismiss-btn:hover {
1232
1189
  background: var(--color-offset-variant);
1233
- color: var(--color-red45);
1190
+ color: var(--color-error);
1234
1191
  }
1235
1192
 
1236
1193
  .ap-notification__actions {
@@ -1316,7 +1273,7 @@
1316
1273
  }
1317
1274
 
1318
1275
  .ap-profile__bio a {
1319
- color: var(--color-primary);
1276
+ color: var(--color-primary-on-background);
1320
1277
  }
1321
1278
 
1322
1279
  /* Override upstream .mention { display: grid } for bio content */
@@ -1463,7 +1420,7 @@
1463
1420
  }
1464
1421
 
1465
1422
  .ap-my-profile__bio a {
1466
- color: var(--color-primary);
1423
+ color: var(--color-primary-on-background);
1467
1424
  }
1468
1425
 
1469
1426
  /* Override upstream .mention { display: grid } for bio content */
@@ -1513,7 +1470,7 @@
1513
1470
  }
1514
1471
 
1515
1472
  .ap-my-profile__field-value a {
1516
- color: var(--color-primary);
1473
+ color: var(--color-primary-on-background);
1517
1474
  }
1518
1475
 
1519
1476
  .ap-my-profile__stats {
@@ -1795,7 +1752,7 @@
1795
1752
  }
1796
1753
 
1797
1754
  .ap-post-detail__back-link {
1798
- color: var(--color-primary);
1755
+ color: var(--color-primary-on-background);
1799
1756
  font-size: var(--font-size-s);
1800
1757
  text-decoration: none;
1801
1758
  }
@@ -1879,10 +1836,10 @@
1879
1836
  }
1880
1837
 
1881
1838
  .ap-tag-header__follow-btn {
1882
- background: var(--color-accent);
1839
+ background: var(--color-primary);
1883
1840
  border: none;
1884
1841
  border-radius: var(--border-radius-small);
1885
- color: var(--color-on-accent);
1842
+ color: var(--color-on-primary, var(--color-neutral99));
1886
1843
  cursor: pointer;
1887
1844
  font-size: var(--font-size-s);
1888
1845
  padding: var(--space-xs) var(--space-s);
@@ -2023,10 +1980,10 @@
2023
1980
  }
2024
1981
 
2025
1982
  .ap-explore-error {
2026
- background: color-mix(in srgb, var(--color-red45) 10%, transparent);
2027
- border: var(--border-width-thin) solid var(--color-red45);
1983
+ background: color-mix(in srgb, var(--color-error) 10%, transparent);
1984
+ border: var(--border-width-thin) solid var(--color-error);
2028
1985
  border-radius: var(--border-radius-small);
2029
- color: var(--color-red45);
1986
+ color: var(--color-error);
2030
1987
  margin-bottom: var(--space-m);
2031
1988
  padding: var(--space-s) var(--space-m);
2032
1989
  }
@@ -2296,7 +2253,7 @@
2296
2253
  }
2297
2254
 
2298
2255
  .ap-tab-control--remove:hover {
2299
- color: var(--color-red45);
2256
+ color: var(--color-error);
2300
2257
  }
2301
2258
 
2302
2259
  /* Truncate long domain names in tab labels */
@@ -2321,13 +2278,13 @@
2321
2278
  }
2322
2279
 
2323
2280
  .ap-tab__badge--local {
2324
- background: color-mix(in srgb, var(--color-blue40, #2563eb) 15%, transparent);
2325
- color: var(--color-blue40, #2563eb);
2281
+ background: color-mix(in srgb, var(--color-primary) 15%, transparent);
2282
+ color: var(--color-primary-on-background);
2326
2283
  }
2327
2284
 
2328
2285
  .ap-tab__badge--federated {
2329
- background: color-mix(in srgb, var(--color-purple45, #7c3aed) 15%, transparent);
2330
- color: var(--color-purple45, #7c3aed);
2286
+ background: color-mix(in srgb, var(--color-purple45) 15%, transparent);
2287
+ color: var(--color-purple45);
2331
2288
  }
2332
2289
 
2333
2290
  /* +# button for adding hashtag tabs */
@@ -2393,9 +2350,9 @@
2393
2350
 
2394
2351
  .ap-explore-pin-btn {
2395
2352
  background: none;
2396
- border: var(--border-width-thin) solid var(--color-primary);
2353
+ border: var(--border-width-thin) solid var(--color-primary-on-background);
2397
2354
  border-radius: var(--border-radius-small);
2398
- color: var(--color-primary);
2355
+ color: var(--color-primary-on-background);
2399
2356
  cursor: pointer;
2400
2357
  font-family: inherit;
2401
2358
  font-size: var(--font-size-s);
@@ -2475,23 +2432,23 @@
2475
2432
  }
2476
2433
 
2477
2434
  .ap-explore-tab-error__message {
2478
- color: var(--color-red45);
2435
+ color: var(--color-error);
2479
2436
  font-size: var(--font-size-s);
2480
2437
  margin: 0;
2481
2438
  }
2482
2439
 
2483
2440
  .ap-explore-tab-error__retry {
2484
2441
  background: none;
2485
- border: 1px solid var(--color-accent);
2442
+ border: 1px solid var(--color-primary-on-background);
2486
2443
  border-radius: var(--border-radius-small);
2487
- color: var(--color-accent);
2444
+ color: var(--color-primary-on-background);
2488
2445
  cursor: pointer;
2489
2446
  font-size: var(--font-size-s);
2490
2447
  padding: var(--space-xs) var(--space-s);
2491
2448
  }
2492
2449
 
2493
2450
  .ap-explore-tab-error__retry:hover {
2494
- background: color-mix(in srgb, var(--color-accent) 10%, transparent);
2451
+ background: color-mix(in srgb, var(--color-primary) 10%, transparent);
2495
2452
  }
2496
2453
 
2497
2454
  /* Empty state */
@@ -2749,3 +2706,98 @@
2749
2706
  z-index: 2;
2750
2707
  }
2751
2708
 
2709
+ /* ==========================================================================
2710
+ Dark Mode Overrides
2711
+ Softens saturated colors that are uncomfortable on dark backgrounds.
2712
+ Uses Indiekit's existing light-variant tokens (red80, green90, yellow90)
2713
+ which are designed for dark surfaces.
2714
+ ========================================================================== */
2715
+
2716
+ @media (prefers-color-scheme: dark) {
2717
+
2718
+ /* --- Action button hover states: softer colors, more visible tinted backgrounds --- */
2719
+ .ap-card__action--reply:hover {
2720
+ background: color-mix(in srgb, var(--color-primary) 18%, transparent);
2721
+ color: var(--color-primary-on-background);
2722
+ }
2723
+
2724
+ .ap-card__action--boost:hover {
2725
+ background: color-mix(in srgb, var(--color-green50) 18%, transparent);
2726
+ color: var(--color-green90);
2727
+ }
2728
+
2729
+ .ap-card__action--like:hover {
2730
+ background: color-mix(in srgb, var(--color-red45) 18%, transparent);
2731
+ color: var(--color-red80);
2732
+ }
2733
+
2734
+ .ap-card__action--save:hover {
2735
+ background: color-mix(in srgb, var(--color-primary) 18%, transparent);
2736
+ color: var(--color-primary-on-background);
2737
+ }
2738
+
2739
+ /* --- Active interaction states --- */
2740
+ .ap-card__action--like.ap-card__action--active {
2741
+ background: color-mix(in srgb, var(--color-red45) 18%, transparent);
2742
+ color: var(--color-red80);
2743
+ }
2744
+
2745
+ .ap-card__action--boost.ap-card__action--active {
2746
+ background: color-mix(in srgb, var(--color-green50) 18%, transparent);
2747
+ color: var(--color-green90);
2748
+ }
2749
+
2750
+ .ap-card__action--save.ap-card__action--active {
2751
+ background: color-mix(in srgb, var(--color-primary) 18%, transparent);
2752
+ color: var(--color-primary-on-background);
2753
+ }
2754
+
2755
+ /* --- Post-type left border accents: desaturated for dark surfaces --- */
2756
+ .ap-card--note,
2757
+ .ap-card--note:hover {
2758
+ border-left-color: var(--color-purple90);
2759
+ }
2760
+
2761
+ .ap-card--article,
2762
+ .ap-card--article:hover {
2763
+ border-left-color: var(--color-green90);
2764
+ }
2765
+
2766
+ .ap-card--boost,
2767
+ .ap-card--boost:hover {
2768
+ border-left-color: var(--color-yellow90);
2769
+ }
2770
+
2771
+ .ap-card--reply,
2772
+ .ap-card--reply:hover {
2773
+ border-left-color: var(--color-primary-on-background);
2774
+ }
2775
+
2776
+ /* --- Notification unread glow: toned down --- */
2777
+ .ap-notification--unread {
2778
+ border-color: var(--color-yellow90);
2779
+ box-shadow: 0 0 6px 0 color-mix(in srgb, var(--color-yellow50) 15%, transparent);
2780
+ }
2781
+
2782
+ /* --- Post detail highlight ring: softened --- */
2783
+ .ap-post-detail__main .ap-card {
2784
+ border-color: color-mix(in srgb, var(--color-primary) 50%, transparent);
2785
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-primary) 50%, transparent);
2786
+ }
2787
+
2788
+ /* --- Card shadows: use light tint instead of black --- */
2789
+ .ap-card {
2790
+ box-shadow: 0 1px 2px rgba(255, 255, 255, 0.04);
2791
+ }
2792
+
2793
+ .ap-card:hover {
2794
+ box-shadow: 0 2px 8px rgba(255, 255, 255, 0.06);
2795
+ }
2796
+
2797
+ /* --- Tab badge federated: soften purple --- */
2798
+ .ap-tab__badge--federated {
2799
+ color: var(--color-purple90);
2800
+ background: color-mix(in srgb, var(--color-purple45) 18%, transparent);
2801
+ }
2802
+ }
2803
+
package/index.js CHANGED
@@ -80,7 +80,6 @@ import { hashtagExploreApiController } from "./lib/controllers/hashtag-explore.j
80
80
  import { publicProfileController } from "./lib/controllers/public-profile.js";
81
81
  import { authorizeInteractionController } from "./lib/controllers/authorize-interaction.js";
82
82
  import { myProfileController } from "./lib/controllers/my-profile.js";
83
- import { noteObjectController } from "./lib/controllers/note-object.js";
84
83
  import {
85
84
  refollowPauseController,
86
85
  refollowResumeController,
@@ -189,10 +188,6 @@ export default class ActivityPubEndpoint {
189
188
  return self._fedifyMiddleware(req, res, next);
190
189
  });
191
190
 
192
- // Serve stored quick reply Notes as JSON-LD so remote servers can
193
- // dereference the Note ID during Create activity verification.
194
- router.get("/quick-replies/:id", noteObjectController(self));
195
-
196
191
  // Authorize interaction — remote follow / subscribe endpoint.
197
192
  // Remote servers redirect users here via the WebFinger subscribe template.
198
193
  router.get("/authorize_interaction", authorizeInteractionController(self));
@@ -889,7 +884,6 @@ export default class ActivityPubEndpoint {
889
884
  Indiekit.addCollection("ap_muted");
890
885
  Indiekit.addCollection("ap_blocked");
891
886
  Indiekit.addCollection("ap_interactions");
892
- Indiekit.addCollection("ap_notes");
893
887
  Indiekit.addCollection("ap_followed_tags");
894
888
  // Explore tab collections
895
889
  Indiekit.addCollection("ap_explore_tabs");
@@ -911,7 +905,6 @@ export default class ActivityPubEndpoint {
911
905
  ap_muted: indiekitCollections.get("ap_muted"),
912
906
  ap_blocked: indiekitCollections.get("ap_blocked"),
913
907
  ap_interactions: indiekitCollections.get("ap_interactions"),
914
- ap_notes: indiekitCollections.get("ap_notes"),
915
908
  ap_followed_tags: indiekitCollections.get("ap_followed_tags"),
916
909
  // Explore tab collections
917
910
  ap_explore_tabs: indiekitCollections.get("ap_explore_tabs"),
@@ -1,11 +1,9 @@
1
1
  /**
2
- * Compose controllers — reply form via Micropub or direct AP.
2
+ * Compose controllers — reply form via Micropub.
3
3
  */
4
4
 
5
- import { Temporal } from "@js-temporal/polyfill";
6
5
  import { getToken, validateToken } from "../csrf.js";
7
6
  import { sanitizeContent } from "../timeline-store.js";
8
- import { resolveAuthor } from "../resolve-author.js";
9
7
 
10
8
  /**
11
9
  * Fetch syndication targets from the Micropub config endpoint.
@@ -155,7 +153,7 @@ export function composeController(mountPath, plugin) {
155
153
  }
156
154
 
157
155
  /**
158
- * POST /admin/reader/compose — Submit reply via Micropub or direct AP.
156
+ * POST /admin/reader/compose — Submit reply via Micropub.
159
157
  * @param {string} mountPath - Plugin mount path
160
158
  * @param {object} plugin - ActivityPub plugin instance
161
159
  */
@@ -170,7 +168,7 @@ export function submitComposeController(mountPath, plugin) {
170
168
  }
171
169
 
172
170
  const { application } = request.app.locals;
173
- const { content, mode } = request.body;
171
+ const { content } = request.body;
174
172
  const inReplyTo = request.body["in-reply-to"];
175
173
  const syndicateTo = request.body["mp-syndicate-to"];
176
174
 
@@ -181,122 +179,7 @@ export function submitComposeController(mountPath, plugin) {
181
179
  });
182
180
  }
183
181
 
184
- // Quick reply direct AP
185
- if (mode === "quick") {
186
- if (!plugin._federation) {
187
- return response.status(503).render("error", {
188
- title: "Error",
189
- content: "Federation not initialized",
190
- });
191
- }
192
-
193
- const { Create, Note } = await import("@fedify/fedify/vocab");
194
- const handle = plugin.options.actor.handle;
195
- const ctx = plugin._federation.createContext(
196
- new URL(plugin._publicationUrl),
197
- { handle, publicationUrl: plugin._publicationUrl },
198
- );
199
-
200
- const uuid = crypto.randomUUID();
201
- const baseUrl = plugin._publicationUrl.replace(/\/$/, "");
202
- const noteId = `${baseUrl}/activitypub/quick-replies/${uuid}`;
203
- const actorUri = ctx.getActorUri(handle);
204
-
205
- const publicAddress = new URL(
206
- "https://www.w3.org/ns/activitystreams#Public",
207
- );
208
- const followersUri = ctx.getFollowersUri(handle);
209
-
210
- const documentLoader = await ctx.getDocumentLoader({
211
- identifier: handle,
212
- });
213
-
214
- // Resolve the original author BEFORE constructing the Note,
215
- // so we can include them in cc (required for threading/notification)
216
- let recipient = null;
217
- if (inReplyTo) {
218
- recipient = await resolveAuthor(
219
- inReplyTo,
220
- ctx,
221
- documentLoader,
222
- application?.collections,
223
- );
224
- }
225
-
226
- // Build cc list: always include followers, add original author for replies
227
- const ccList = [followersUri];
228
- if (recipient?.id) {
229
- ccList.push(recipient.id);
230
- }
231
-
232
- const note = new Note({
233
- id: new URL(noteId),
234
- attribution: actorUri,
235
- content: content.trim(),
236
- replyTarget: inReplyTo ? new URL(inReplyTo) : undefined,
237
- published: Temporal.Now.instant(),
238
- to: publicAddress,
239
- ccs: ccList,
240
- });
241
-
242
- const create = new Create({
243
- id: new URL(`${noteId}#activity`),
244
- actor: actorUri,
245
- object: note,
246
- to: publicAddress,
247
- ccs: ccList,
248
- });
249
-
250
- // Store the Note so remote servers can dereference its ID
251
- const ap_notes = application?.collections?.get("ap_notes");
252
- if (ap_notes) {
253
- await ap_notes.insertOne({
254
- _id: uuid,
255
- noteId,
256
- actorUrl: actorUri.href,
257
- content: content.trim(),
258
- inReplyTo: inReplyTo || null,
259
- published: new Date().toISOString(),
260
- to: ["https://www.w3.org/ns/activitystreams#Public"],
261
- cc: ccList.map((u) => (u instanceof URL ? u.href : u.href || u)),
262
- });
263
- }
264
-
265
- // Send to followers
266
- await ctx.sendActivity({ identifier: handle }, "followers", create, {
267
- preferSharedInbox: true,
268
- syncCollection: true,
269
- orderingKey: noteId,
270
- });
271
-
272
- // Also send directly to the original author's inbox
273
- if (recipient) {
274
- try {
275
- await ctx.sendActivity(
276
- { identifier: handle },
277
- recipient,
278
- create,
279
- { orderingKey: noteId },
280
- );
281
- console.info(
282
- `[ActivityPub] Sent quick reply directly to ${recipient.id?.href || "author"}`,
283
- );
284
- } catch (error) {
285
- console.warn(
286
- `[ActivityPub] Direct delivery to author failed (quick reply):`,
287
- error.message,
288
- );
289
- }
290
- }
291
-
292
- console.info(
293
- `[ActivityPub] Sent quick reply${inReplyTo ? ` to ${inReplyTo}` : ""}`,
294
- );
295
-
296
- return response.redirect(`${mountPath}/admin/reader`);
297
- }
298
-
299
- // Micropub path — post as blog reply
182
+ // Post as blog reply via Micropub
300
183
  const micropubEndpoint = application.micropubEndpoint;
301
184
 
302
185
  if (!micropubEndpoint) {
package/locales/en.json CHANGED
@@ -141,15 +141,9 @@
141
141
  },
142
142
  "compose": {
143
143
  "title": "Compose reply",
144
- "modeLabel": "Reply mode",
145
- "modeMicropub": "Post as blog reply",
146
- "modeMicropubHint": "Creates a permanent post on your blog, syndicated to the fediverse",
147
- "modeQuick": "Quick reply",
148
- "modeQuickHint": "Sends a reply directly to the fediverse (no blog post created)",
149
144
  "placeholder": "Write your reply…",
150
145
  "syndicateLabel": "Syndicate to",
151
146
  "submitMicropub": "Post reply",
152
- "submitQuick": "Send reply",
153
147
  "cancel": "Cancel",
154
148
  "errorEmpty": "Reply content cannot be empty"
155
149
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-activitypub",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
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",
@@ -21,50 +21,23 @@
21
21
  </div>
22
22
  {% endif %}
23
23
 
24
- <form method="post" action="{{ mountPath }}/admin/reader/compose" class="ap-compose__form"
25
- x-data="{
26
- mode: 'micropub',
27
- content: '',
28
- maxChars: 500,
29
- get remaining() { return this.maxChars - this.content.length; }
30
- }">
24
+ <form method="post" action="{{ mountPath }}/admin/reader/compose" class="ap-compose__form">
31
25
  <input type="hidden" name="_csrf" value="{{ csrfToken }}">
32
26
  {% if replyTo %}
33
27
  <input type="hidden" name="in-reply-to" value="{{ replyTo }}">
34
28
  {% endif %}
35
29
 
36
- {# Mode toggle #}
37
- <fieldset class="ap-compose__mode">
38
- <legend>{{ __("activitypub.compose.modeLabel") }}</legend>
39
- <label class="ap-compose__mode-option">
40
- <input type="radio" name="mode" value="micropub" x-model="mode" checked>
41
- {{ __("activitypub.compose.modeMicropub") }}
42
- <span class="ap-compose__mode-hint">{{ __("activitypub.compose.modeMicropubHint") }}</span>
43
- </label>
44
- <label class="ap-compose__mode-option">
45
- <input type="radio" name="mode" value="quick" x-model="mode">
46
- {{ __("activitypub.compose.modeQuick") }}
47
- <span class="ap-compose__mode-hint">{{ __("activitypub.compose.modeQuickHint") }}</span>
48
- </label>
49
- </fieldset>
50
-
51
30
  {# Content textarea #}
52
31
  <div class="ap-compose__editor">
53
32
  <textarea name="content" class="ap-compose__textarea"
54
33
  rows="6"
55
- :maxlength="mode === 'quick' ? maxChars : undefined"
56
- x-model="content"
57
34
  placeholder="{{ __('activitypub.compose.placeholder') }}"
58
35
  required></textarea>
59
- <div class="ap-compose__counter" x-show="mode === 'quick'" x-cloak>
60
- <span :class="{ 'ap-compose__counter--warn': remaining < 50, 'ap-compose__counter--over': remaining < 0 }"
61
- x-text="remaining"></span>
62
- </div>
63
36
  </div>
64
37
 
65
- {# Syndication targets (Micropub mode only) #}
38
+ {# Syndication targets #}
66
39
  {% if syndicationTargets.length > 0 %}
67
- <fieldset class="ap-compose__syndication" x-show="mode === 'micropub'">
40
+ <fieldset class="ap-compose__syndication">
68
41
  <legend>{{ __("activitypub.compose.syndicateLabel") }}</legend>
69
42
  {% for target in syndicationTargets %}
70
43
  <label class="ap-compose__syndication-target">
@@ -77,8 +50,7 @@
77
50
 
78
51
  <div class="ap-compose__actions">
79
52
  <button type="submit" class="ap-compose__submit">
80
- <span x-show="mode === 'micropub'">{{ __("activitypub.compose.submitMicropub") }}</span>
81
- <span x-show="mode === 'quick'">{{ __("activitypub.compose.submitQuick") }}</span>
53
+ {{ __("activitypub.compose.submitMicropub") }}
82
54
  </button>
83
55
  <a href="{{ mountPath }}/admin/reader" class="ap-compose__cancel">
84
56
  {{ __("activitypub.compose.cancel") }}
@@ -1,51 +0,0 @@
1
- /**
2
- * Public route handler for serving quick reply Notes as ActivityPub JSON-LD.
3
- *
4
- * Remote servers dereference Note IDs to verify Create activities.
5
- * Without this, quick replies are rejected by servers that validate
6
- * the Note's ID URL (Mastodon with Authorized Fetch, Bonfire, etc.).
7
- */
8
-
9
- /**
10
- * GET /quick-replies/:id — serve a stored Note as JSON-LD.
11
- * @param {object} plugin - ActivityPub plugin instance
12
- */
13
- export function noteObjectController(plugin) {
14
- return async (request, response) => {
15
- const { id } = request.params;
16
-
17
- const { application } = request.app.locals;
18
- const ap_notes = application?.collections?.get("ap_notes");
19
-
20
- if (!ap_notes) {
21
- return response.status(404).json({ error: "Not Found" });
22
- }
23
-
24
- const note = await ap_notes.findOne({ _id: id });
25
-
26
- if (!note) {
27
- return response.status(404).json({ error: "Not Found" });
28
- }
29
-
30
- const noteJson = {
31
- "@context": "https://www.w3.org/ns/activitystreams",
32
- id: note.noteId,
33
- type: "Note",
34
- attributedTo: note.actorUrl,
35
- content: note.content,
36
- published: note.published,
37
- to: note.to,
38
- cc: note.cc,
39
- };
40
-
41
- if (note.inReplyTo) {
42
- noteJson.inReplyTo = note.inReplyTo;
43
- }
44
-
45
- response
46
- .status(200)
47
- .set("Content-Type", "application/activity+json; charset=utf-8")
48
- .set("Cache-Control", "public, max-age=3600")
49
- .json(noteJson);
50
- };
51
- }