@rmdes/indiekit-endpoint-activitypub 3.8.6 → 3.9.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.
Files changed (52) hide show
  1. package/assets/css/base.css +144 -0
  2. package/assets/css/card.css +377 -0
  3. package/assets/css/compose.css +169 -0
  4. package/assets/css/dark-mode.css +94 -0
  5. package/assets/css/explore.css +530 -0
  6. package/assets/css/features.css +436 -0
  7. package/assets/css/federation.css +242 -0
  8. package/assets/css/interactions.css +236 -0
  9. package/assets/css/media.css +315 -0
  10. package/assets/css/messages.css +158 -0
  11. package/assets/css/moderation.css +119 -0
  12. package/assets/css/notifications.css +191 -0
  13. package/assets/css/profile.css +308 -0
  14. package/assets/css/responsive.css +33 -0
  15. package/assets/css/skeleton.css +74 -0
  16. package/assets/reader-interactions.js +115 -0
  17. package/assets/reader.css +20 -3439
  18. package/index.js +34 -694
  19. package/lib/batch-broadcast.js +98 -0
  20. package/lib/controllers/compose.js +5 -7
  21. package/lib/controllers/interactions-boost.js +8 -13
  22. package/lib/controllers/interactions-like.js +8 -13
  23. package/lib/federation-actions.js +70 -0
  24. package/lib/inbox-queue.js +16 -10
  25. package/lib/init-indexes.js +251 -0
  26. package/lib/item-processing.js +22 -2
  27. package/lib/lookup-cache.js +3 -0
  28. package/lib/mastodon/backfill-timeline.js +11 -2
  29. package/lib/mastodon/entities/sanitize.js +19 -88
  30. package/lib/mastodon/helpers/account-cache.js +3 -0
  31. package/lib/mastodon/helpers/enrich-accounts.js +42 -55
  32. package/lib/mastodon/router.js +31 -0
  33. package/lib/mastodon/routes/accounts.js +16 -49
  34. package/lib/mastodon/routes/media.js +6 -4
  35. package/lib/mastodon/routes/notifications.js +6 -24
  36. package/lib/mastodon/routes/oauth.js +91 -18
  37. package/lib/mastodon/routes/search.js +3 -1
  38. package/lib/mastodon/routes/statuses.js +14 -52
  39. package/lib/mastodon/routes/timelines.js +3 -6
  40. package/lib/og-unfurl.js +52 -33
  41. package/lib/storage/moderation.js +11 -2
  42. package/lib/syndicator.js +239 -0
  43. package/lib/timeline-store.js +11 -15
  44. package/package.json +2 -1
  45. package/views/activitypub-federation-mgmt.njk +2 -2
  46. package/views/activitypub-moderation.njk +1 -1
  47. package/views/activitypub-profile.njk +16 -76
  48. package/views/activitypub-reader.njk +2 -1
  49. package/views/layouts/ap-reader.njk +2 -0
  50. package/views/partials/ap-item-card.njk +14 -117
  51. package/views/partials/ap-item-content.njk +20 -0
  52. package/views/partials/ap-notification-card.njk +1 -1
@@ -0,0 +1,436 @@
1
+ /* ==========================================================================
2
+ Post Detail View — Thread Layout
3
+ ========================================================================== */
4
+
5
+ .ap-post-detail__back {
6
+ margin-bottom: var(--space-m);
7
+ }
8
+
9
+ .ap-post-detail__back-link {
10
+ color: var(--color-primary-on-background);
11
+ font-size: var(--font-size-s);
12
+ text-decoration: none;
13
+ }
14
+
15
+ .ap-post-detail__back-link:hover {
16
+ text-decoration: underline;
17
+ }
18
+
19
+ .ap-post-detail__not-found {
20
+ background: var(--color-offset);
21
+ border-radius: var(--border-radius-small);
22
+ color: var(--color-on-offset);
23
+ padding: var(--space-l);
24
+ text-align: center;
25
+ }
26
+
27
+ .ap-post-detail__section-title {
28
+ color: var(--color-on-offset);
29
+ font-size: var(--font-size-s);
30
+ font-weight: 600;
31
+ margin: var(--space-m) 0 var(--space-s);
32
+ padding-bottom: var(--space-xs);
33
+ text-transform: uppercase;
34
+ letter-spacing: 0.05em;
35
+ }
36
+
37
+ /* Parent posts — indented with left border to show thread chain */
38
+ .ap-post-detail__parents {
39
+ border-left: 3px solid var(--color-outline);
40
+ margin-bottom: var(--space-s);
41
+ padding-left: var(--space-m);
42
+ }
43
+
44
+ .ap-post-detail__parent-item .ap-card {
45
+ opacity: 0.85;
46
+ }
47
+
48
+ /* Main post — highlighted */
49
+ .ap-post-detail__main {
50
+ margin-bottom: var(--space-m);
51
+ }
52
+
53
+ .ap-post-detail__main .ap-card {
54
+ border-color: var(--color-primary);
55
+ box-shadow: 0 0 0 1px var(--color-primary);
56
+ }
57
+
58
+ /* Replies — indented from the other side */
59
+ .ap-post-detail__replies {
60
+ margin-left: var(--space-l);
61
+ }
62
+
63
+ .ap-post-detail__reply-item {
64
+ border-left: 2px solid var(--color-outline);
65
+ padding-left: var(--space-m);
66
+ margin-bottom: var(--space-xs);
67
+ }
68
+
69
+ /* ==========================================================================
70
+ Tag Timeline Header
71
+ ========================================================================== */
72
+
73
+ .ap-tag-header {
74
+ align-items: flex-start;
75
+ background: var(--color-offset);
76
+ border-bottom: var(--border-width-thin) solid var(--color-outline);
77
+ border-radius: var(--border-radius-small);
78
+ display: flex;
79
+ gap: var(--space-m);
80
+ justify-content: space-between;
81
+ margin-bottom: var(--space-m);
82
+ padding: var(--space-m);
83
+ }
84
+
85
+ .ap-tag-header__title {
86
+ font-size: var(--font-size-xl);
87
+ font-weight: 600;
88
+ margin: 0 0 var(--space-xs);
89
+ }
90
+
91
+ .ap-tag-header__count {
92
+ color: var(--color-on-offset);
93
+ font-size: var(--font-size-s);
94
+ margin: 0;
95
+ }
96
+
97
+ .ap-tag-header__actions {
98
+ align-items: center;
99
+ display: flex;
100
+ flex-shrink: 0;
101
+ gap: var(--space-s);
102
+ }
103
+
104
+ .ap-tag-header__follow-btn {
105
+ background: var(--color-primary);
106
+ border: none;
107
+ border-radius: var(--border-radius-small);
108
+ color: var(--color-on-primary, var(--color-neutral99));
109
+ cursor: pointer;
110
+ font-size: var(--font-size-s);
111
+ padding: var(--space-xs) var(--space-s);
112
+ }
113
+
114
+ .ap-tag-header__follow-btn:hover {
115
+ opacity: 0.85;
116
+ }
117
+
118
+ .ap-tag-header__unfollow-btn {
119
+ background: transparent;
120
+ border: var(--border-width-thin) solid var(--color-outline);
121
+ border-radius: var(--border-radius-small);
122
+ color: var(--color-on-background);
123
+ cursor: pointer;
124
+ font-size: var(--font-size-s);
125
+ padding: var(--space-xs) var(--space-s);
126
+ }
127
+
128
+ .ap-tag-header__unfollow-btn:hover {
129
+ border-color: var(--color-on-background);
130
+ }
131
+
132
+ .ap-tag-header__back {
133
+ color: var(--color-on-offset);
134
+ font-size: var(--font-size-s);
135
+ text-decoration: none;
136
+ }
137
+
138
+ .ap-tag-header__back:hover {
139
+ color: var(--color-on-background);
140
+ text-decoration: underline;
141
+ }
142
+
143
+ @media (max-width: 640px) {
144
+ .ap-tag-header {
145
+ flex-direction: column;
146
+ gap: var(--space-s);
147
+ }
148
+
149
+ .ap-tag-header__actions {
150
+ flex-wrap: wrap;
151
+ }
152
+ }
153
+
154
+ /* ==========================================================================
155
+ Reader Tools Bar (Explore link, etc.)
156
+ ========================================================================== */
157
+
158
+ .ap-reader-tools {
159
+ display: flex;
160
+ gap: var(--space-s);
161
+ justify-content: flex-end;
162
+ margin-bottom: var(--space-s);
163
+ }
164
+
165
+ .ap-reader-tools__explore {
166
+ color: var(--color-on-offset);
167
+ font-size: var(--font-size-s);
168
+ text-decoration: none;
169
+ }
170
+
171
+ .ap-reader-tools__explore:hover {
172
+ color: var(--color-on-background);
173
+ text-decoration: underline;
174
+ }
175
+
176
+ /* Followed tags bar */
177
+ .ap-followed-tags {
178
+ display: flex;
179
+ flex-wrap: wrap;
180
+ align-items: center;
181
+ gap: var(--space-xs);
182
+ padding: var(--space-xs) 0;
183
+ margin-bottom: var(--space-s);
184
+ font-size: var(--font-size-s);
185
+ }
186
+
187
+ .ap-followed-tags__label {
188
+ color: var(--color-on-offset);
189
+ font-weight: 600;
190
+ }
191
+
192
+ /* ==========================================================================
193
+ New Posts Banner
194
+ ========================================================================== */
195
+
196
+ .ap-new-posts-banner {
197
+ left: 0;
198
+ position: sticky;
199
+ right: 0;
200
+ top: 0;
201
+ z-index: 10;
202
+ }
203
+
204
+ .ap-new-posts-banner__btn {
205
+ background: var(--color-primary);
206
+ border: none;
207
+ border-radius: var(--border-radius-small);
208
+ color: var(--color-on-primary);
209
+ cursor: pointer;
210
+ display: block;
211
+ font-family: inherit;
212
+ font-size: var(--font-size-s);
213
+ margin: 0 auto var(--space-s);
214
+ padding: var(--space-xs) var(--space-m);
215
+ text-align: center;
216
+ width: auto;
217
+ }
218
+
219
+ .ap-new-posts-banner__btn:hover {
220
+ opacity: 0.9;
221
+ }
222
+
223
+ /* ==========================================================================
224
+ Read State
225
+ ========================================================================== */
226
+
227
+ .ap-card--read {
228
+ opacity: 0.7;
229
+ transition: opacity 0.3s ease;
230
+ }
231
+
232
+ .ap-card--read:hover {
233
+ opacity: 1;
234
+ }
235
+
236
+ /* ==========================================================================
237
+ Unread Toggle
238
+ ========================================================================== */
239
+
240
+ .ap-unread-toggle {
241
+ margin-left: auto;
242
+ }
243
+
244
+ .ap-unread-toggle--active {
245
+ background: color-mix(in srgb, var(--color-primary) 12%, transparent);
246
+ font-weight: 600;
247
+ }
248
+
249
+ /* ==========================================================================
250
+ Quote Embeds
251
+ ========================================================================== */
252
+
253
+ .ap-quote-embed {
254
+ border: var(--border-width-thin) solid var(--color-outline);
255
+ border-radius: var(--border-radius-small);
256
+ margin-top: var(--space-s);
257
+ overflow: hidden;
258
+ transition: border-color 0.15s ease;
259
+ }
260
+
261
+ .ap-quote-embed:hover {
262
+ border-color: var(--color-outline-variant);
263
+ }
264
+
265
+ .ap-quote-embed--pending {
266
+ border-style: dashed;
267
+ }
268
+
269
+ .ap-quote-embed__link {
270
+ color: inherit;
271
+ display: block;
272
+ padding: var(--space-s) var(--space-m);
273
+ text-decoration: none;
274
+ }
275
+
276
+ .ap-quote-embed__link:hover {
277
+ background: color-mix(in srgb, var(--color-offset) 50%, transparent);
278
+ }
279
+
280
+ .ap-quote-embed__author {
281
+ align-items: center;
282
+ display: flex;
283
+ gap: var(--space-xs);
284
+ margin-bottom: var(--space-xs);
285
+ }
286
+
287
+ .ap-quote-embed__avatar {
288
+ border-radius: 50%;
289
+ flex-shrink: 0;
290
+ height: 24px;
291
+ object-fit: cover;
292
+ width: 24px;
293
+ }
294
+
295
+ .ap-quote-embed__avatar--default {
296
+ align-items: center;
297
+ background: var(--color-offset);
298
+ color: var(--color-on-offset);
299
+ display: inline-flex;
300
+ font-size: var(--font-size-xs);
301
+ font-weight: 600;
302
+ justify-content: center;
303
+ }
304
+
305
+ .ap-quote-embed__author-info {
306
+ flex: 1;
307
+ min-width: 0;
308
+ }
309
+
310
+ .ap-quote-embed__name {
311
+ font-size: var(--font-size-s);
312
+ font-weight: 600;
313
+ overflow: hidden;
314
+ text-overflow: ellipsis;
315
+ white-space: nowrap;
316
+ }
317
+
318
+ .ap-quote-embed__handle {
319
+ color: var(--color-on-offset);
320
+ font-size: var(--font-size-xs);
321
+ overflow: hidden;
322
+ text-overflow: ellipsis;
323
+ white-space: nowrap;
324
+ }
325
+
326
+ .ap-quote-embed__time {
327
+ color: var(--color-on-offset);
328
+ flex-shrink: 0;
329
+ font-size: var(--font-size-xs);
330
+ white-space: nowrap;
331
+ }
332
+
333
+ .ap-quote-embed__title {
334
+ font-size: var(--font-size-s);
335
+ font-weight: 600;
336
+ margin: 0 0 var(--space-xs);
337
+ }
338
+
339
+ .ap-quote-embed__content {
340
+ color: var(--color-on-background);
341
+ font-size: var(--font-size-s);
342
+ line-height: calc(4 / 3 * 1em);
343
+ max-height: calc(1.333em * 6);
344
+ overflow: hidden;
345
+ }
346
+
347
+ .ap-quote-embed__content a {
348
+ display: inline;
349
+ }
350
+
351
+ .ap-quote-embed__content a span {
352
+ display: inline;
353
+ }
354
+
355
+ .ap-quote-embed__content p {
356
+ margin: 0 0 var(--space-xs);
357
+ }
358
+
359
+ .ap-quote-embed__content p:last-child {
360
+ margin-bottom: 0;
361
+ }
362
+
363
+ .ap-quote-embed__media {
364
+ margin-top: var(--space-xs);
365
+ }
366
+
367
+ .ap-quote-embed__photo {
368
+ border-radius: var(--border-radius-small);
369
+ max-height: 160px;
370
+ max-width: 100%;
371
+ object-fit: cover;
372
+ }
373
+
374
+ /* ==========================================================================
375
+ Poll / Question
376
+ ========================================================================== */
377
+
378
+ .ap-poll {
379
+ margin-top: var(--space-s);
380
+ }
381
+
382
+ .ap-poll__option {
383
+ position: relative;
384
+ padding: var(--space-xs) var(--space-s);
385
+ margin-bottom: var(--space-xs);
386
+ border-radius: var(--border-radius-small);
387
+ background: var(--color-offset);
388
+ overflow: hidden;
389
+ }
390
+
391
+ .ap-poll__bar {
392
+ position: absolute;
393
+ top: 0;
394
+ left: 0;
395
+ bottom: 0;
396
+ background: var(--color-primary);
397
+ opacity: 0.15;
398
+ border-radius: var(--border-radius-small);
399
+ }
400
+
401
+ .ap-poll__label {
402
+ position: relative;
403
+ font-size: var(--font-size-s);
404
+ color: var(--color-on-background);
405
+ }
406
+
407
+ .ap-poll__votes {
408
+ position: relative;
409
+ float: right;
410
+ font-size: var(--font-size-s);
411
+ font-weight: 600;
412
+ color: var(--color-on-offset);
413
+ }
414
+
415
+ .ap-poll__footer {
416
+ font-size: var(--font-size-xs);
417
+ color: var(--color-on-offset);
418
+ margin-top: var(--space-xs);
419
+ }
420
+
421
+ /* Hashtag tab sources info line */
422
+ .ap-hashtag-sources {
423
+ color: var(--color-on-offset);
424
+ font-size: var(--font-size-s);
425
+ margin: 0;
426
+ padding: var(--space-s) 0 var(--space-xs);
427
+ }
428
+
429
+ /* Custom emoji */
430
+ .ap-custom-emoji {
431
+ height: 1.2em;
432
+ width: auto;
433
+ vertical-align: middle;
434
+ display: inline;
435
+ margin: 0 0.05em;
436
+ }
@@ -0,0 +1,242 @@
1
+ /* ==========================================================================
2
+ Federation Management
3
+ ========================================================================== */
4
+
5
+ .ap-federation__section {
6
+ margin-block-end: var(--space-l);
7
+ }
8
+
9
+ .ap-federation__section h2 {
10
+ margin-block-end: var(--space-s);
11
+ }
12
+
13
+ .ap-federation__stats-grid {
14
+ display: grid;
15
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
16
+ gap: var(--space-s);
17
+ }
18
+
19
+ .ap-federation__stat-card {
20
+ display: flex;
21
+ flex-direction: column;
22
+ align-items: center;
23
+ gap: var(--space-xs);
24
+ padding: var(--space-s);
25
+ background: var(--color-offset);
26
+ border-radius: var(--border-radius-small);
27
+ text-align: center;
28
+ }
29
+
30
+ .ap-federation__stat-count {
31
+ font-size: var(--font-size-xl);
32
+ font-weight: 600;
33
+ color: var(--color-on-background);
34
+ }
35
+
36
+ .ap-federation__stat-label {
37
+ font-size: var(--font-size-s);
38
+ color: var(--color-on-offset);
39
+ word-break: break-word;
40
+ }
41
+
42
+ .ap-federation__actions-row {
43
+ display: flex;
44
+ flex-wrap: wrap;
45
+ gap: var(--space-s);
46
+ align-items: center;
47
+ }
48
+
49
+ .ap-federation__result {
50
+ margin-block-start: var(--space-xs);
51
+ color: var(--color-green50);
52
+ font-size: var(--font-size-s);
53
+ }
54
+
55
+ .ap-federation__error {
56
+ margin-block-start: var(--space-xs);
57
+ color: var(--color-red45);
58
+ font-size: var(--font-size-s);
59
+ }
60
+
61
+ .ap-federation__lookup-form {
62
+ display: flex;
63
+ gap: var(--space-s);
64
+ }
65
+
66
+ .ap-federation__lookup-input {
67
+ flex: 1;
68
+ min-width: 0;
69
+ padding: 0.5rem 0.75rem;
70
+ border: var(--border-width-thin) solid var(--color-outline);
71
+ border-radius: var(--border-radius-small);
72
+ font: inherit;
73
+ color: var(--color-on-background);
74
+ background: var(--color-background);
75
+ }
76
+
77
+ .ap-federation__json-view {
78
+ margin-block-start: var(--space-s);
79
+ padding: var(--space-m);
80
+ background: var(--color-offset);
81
+ border-radius: var(--border-radius-small);
82
+ font-family: monospace;
83
+ font-size: var(--font-size-s);
84
+ color: var(--color-on-background);
85
+ max-height: 24rem;
86
+ overflow: auto;
87
+ white-space: pre-wrap;
88
+ word-break: break-word;
89
+ }
90
+
91
+ .ap-federation__posts-list {
92
+ display: flex;
93
+ flex-direction: column;
94
+ gap: var(--space-xs);
95
+ }
96
+
97
+ .ap-federation__post-row {
98
+ display: flex;
99
+ justify-content: space-between;
100
+ align-items: center;
101
+ gap: var(--space-m);
102
+ padding: var(--space-s);
103
+ background: var(--color-offset);
104
+ border-radius: var(--border-radius-small);
105
+ }
106
+
107
+ .ap-federation__post-info {
108
+ display: flex;
109
+ flex-direction: column;
110
+ gap: var(--space-xs);
111
+ min-width: 0;
112
+ }
113
+
114
+ .ap-federation__post-title {
115
+ font-weight: 500;
116
+ white-space: nowrap;
117
+ overflow: hidden;
118
+ text-overflow: ellipsis;
119
+ }
120
+
121
+ .ap-federation__post-meta {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: var(--space-xs);
125
+ font-size: var(--font-size-s);
126
+ color: var(--color-on-offset);
127
+ }
128
+
129
+ .ap-federation__post-actions {
130
+ display: flex;
131
+ gap: var(--space-xs);
132
+ flex-shrink: 0;
133
+ }
134
+
135
+ .ap-federation__post-btn {
136
+ padding: var(--space-xs) var(--space-s);
137
+ font-size: var(--font-size-s);
138
+ border: var(--border-width-thin) solid var(--color-outline);
139
+ border-radius: var(--border-radius-small);
140
+ background: var(--color-background);
141
+ color: var(--color-on-background);
142
+ cursor: pointer;
143
+ }
144
+
145
+ .ap-federation__post-btn:hover {
146
+ background: var(--color-offset);
147
+ }
148
+
149
+ .ap-federation__post-btn--danger {
150
+ color: var(--color-red45);
151
+ border-color: var(--color-red45);
152
+ }
153
+
154
+ .ap-federation__post-btn--danger:hover {
155
+ background: color-mix(in srgb, var(--color-red45) 10%, transparent);
156
+ }
157
+
158
+ .ap-federation__modal-overlay {
159
+ position: fixed;
160
+ inset: 0;
161
+ z-index: 1000;
162
+ display: flex;
163
+ align-items: center;
164
+ justify-content: center;
165
+ background: hsl(var(--tint-neutral) 10% / 0.5);
166
+ }
167
+
168
+ .ap-federation__modal {
169
+ width: min(90vw, 48rem);
170
+ max-height: 80vh;
171
+ display: flex;
172
+ flex-direction: column;
173
+ background: var(--color-background);
174
+ border-radius: var(--border-radius-small);
175
+ box-shadow: 0 4px 24px hsl(var(--tint-neutral) 10% / 0.2);
176
+ }
177
+
178
+ .ap-federation__modal-header {
179
+ display: flex;
180
+ justify-content: space-between;
181
+ align-items: center;
182
+ padding: var(--space-s) var(--space-m);
183
+ border-block-end: var(--border-width-thin) solid var(--color-outline);
184
+ }
185
+
186
+ .ap-federation__modal-header h3 {
187
+ margin: 0;
188
+ font-size: var(--font-size-m);
189
+ }
190
+
191
+ .ap-federation__modal-close {
192
+ font-size: var(--font-size-xl);
193
+ line-height: 1;
194
+ padding: 0 var(--space-xs);
195
+ border: none;
196
+ background: none;
197
+ color: var(--color-on-offset);
198
+ cursor: pointer;
199
+ }
200
+
201
+ .ap-federation__modal .ap-federation__json-view {
202
+ margin: 0;
203
+ border-radius: 0 0 var(--border-radius-small) var(--border-radius-small);
204
+ flex: 1;
205
+ overflow: auto;
206
+ }
207
+
208
+ @media (max-width: 40rem) {
209
+ .ap-federation__post-row {
210
+ flex-direction: column;
211
+ align-items: flex-start;
212
+ }
213
+
214
+ .ap-federation__lookup-form {
215
+ flex-direction: column;
216
+ }
217
+ }
218
+
219
+ /* Follow request approve/reject actions */
220
+ .ap-follow-request {
221
+ margin-block-end: var(--space-m);
222
+ }
223
+
224
+ .ap-follow-request__actions {
225
+ display: flex;
226
+ gap: var(--space-s);
227
+ margin-block-start: var(--space-xs);
228
+ padding-inline-start: var(--space-l);
229
+ }
230
+
231
+ .ap-follow-request__form {
232
+ display: inline;
233
+ }
234
+
235
+ .button--danger {
236
+ background-color: var(--color-red45);
237
+ color: white;
238
+ }
239
+
240
+ .button--danger:hover {
241
+ background-color: var(--color-red35, #c0392b);
242
+ }