@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.
- package/assets/reader-links.css +4 -4
- package/assets/reader.css +130 -78
- package/index.js +0 -7
- package/lib/controllers/compose.js +4 -121
- package/locales/en.json +0 -6
- package/package.json +1 -1
- package/views/activitypub-compose.njk +4 -32
- package/lib/controllers/note-object.js +0 -51
package/assets/reader-links.css
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
display: flex;
|
|
17
17
|
overflow: hidden;
|
|
18
18
|
border-radius: 8px;
|
|
19
|
-
border: 1px solid var(--color-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
723
|
+
background: color-mix(in srgb, var(--color-primary) 12%, transparent);
|
|
724
724
|
border-radius: var(--border-radius-large);
|
|
725
|
-
color: var(--color-
|
|
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-
|
|
733
|
-
color: var(--color-
|
|
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-
|
|
1080
|
+
color: var(--color-error);
|
|
1124
1081
|
}
|
|
1125
1082
|
|
|
1126
1083
|
.ap-notifications__btn--danger:hover {
|
|
1127
|
-
border-color: var(--color-
|
|
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-
|
|
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-
|
|
1839
|
+
background: var(--color-primary);
|
|
1883
1840
|
border: none;
|
|
1884
1841
|
border-radius: var(--border-radius-small);
|
|
1885
|
-
color: var(--color-on-
|
|
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-
|
|
2027
|
-
border: var(--border-width-thin) solid var(--color-
|
|
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-
|
|
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-
|
|
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-
|
|
2325
|
-
color: var(--color-
|
|
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
|
|
2330
|
-
color: var(--color-purple45
|
|
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-
|
|
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-
|
|
2442
|
+
border: 1px solid var(--color-primary-on-background);
|
|
2486
2443
|
border-radius: var(--border-radius-small);
|
|
2487
|
-
color: var(--color-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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.
|
|
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
|
|
38
|
+
{# Syndication targets #}
|
|
66
39
|
{% if syndicationTargets.length > 0 %}
|
|
67
|
-
<fieldset class="ap-compose__syndication"
|
|
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
|
-
|
|
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
|
-
}
|