@rmdes/indiekit-endpoint-activitypub 3.8.7 → 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 +13 -6
  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,94 @@
1
+ /* ==========================================================================
2
+ Dark Mode Overrides
3
+ Softens saturated colors that are uncomfortable on dark backgrounds.
4
+ Uses Indiekit's existing light-variant tokens (red80, green90, yellow90)
5
+ which are designed for dark surfaces.
6
+ ========================================================================== */
7
+
8
+ @media (prefers-color-scheme: dark) {
9
+
10
+ /* --- Action button hover states: softer colors, more visible tinted backgrounds --- */
11
+ .ap-card__action--reply:hover {
12
+ background: color-mix(in srgb, var(--color-primary) 18%, transparent);
13
+ color: var(--color-primary-on-background);
14
+ }
15
+
16
+ .ap-card__action--boost:hover {
17
+ background: color-mix(in srgb, var(--color-green50) 18%, transparent);
18
+ color: var(--color-green90);
19
+ }
20
+
21
+ .ap-card__action--like:hover {
22
+ background: color-mix(in srgb, var(--color-red45) 18%, transparent);
23
+ color: var(--color-red80);
24
+ }
25
+
26
+ .ap-card__action--save:hover {
27
+ background: color-mix(in srgb, var(--color-primary) 18%, transparent);
28
+ color: var(--color-primary-on-background);
29
+ }
30
+
31
+ /* --- Active interaction states --- */
32
+ .ap-card__action--like.ap-card__action--active {
33
+ background: color-mix(in srgb, var(--color-red45) 18%, transparent);
34
+ color: var(--color-red80);
35
+ }
36
+
37
+ .ap-card__action--boost.ap-card__action--active {
38
+ background: color-mix(in srgb, var(--color-green50) 18%, transparent);
39
+ color: var(--color-green90);
40
+ }
41
+
42
+ .ap-card__action--save.ap-card__action--active {
43
+ background: color-mix(in srgb, var(--color-primary) 18%, transparent);
44
+ color: var(--color-primary-on-background);
45
+ }
46
+
47
+ /* --- Post-type left border accents: desaturated for dark surfaces --- */
48
+ .ap-card--note,
49
+ .ap-card--note:hover {
50
+ border-left-color: var(--color-purple90);
51
+ }
52
+
53
+ .ap-card--article,
54
+ .ap-card--article:hover {
55
+ border-left-color: var(--color-green90);
56
+ }
57
+
58
+ .ap-card--boost,
59
+ .ap-card--boost:hover {
60
+ border-left-color: var(--color-yellow90);
61
+ }
62
+
63
+ .ap-card--reply,
64
+ .ap-card--reply:hover {
65
+ border-left-color: var(--color-primary-on-background);
66
+ }
67
+
68
+ /* --- Notification unread glow: toned down --- */
69
+ .ap-notification--unread {
70
+ border-color: var(--color-yellow90);
71
+ box-shadow: 0 0 6px 0 color-mix(in srgb, var(--color-yellow50) 15%, transparent);
72
+ }
73
+
74
+ /* --- Post detail highlight ring: softened --- */
75
+ .ap-post-detail__main .ap-card {
76
+ border-color: color-mix(in srgb, var(--color-primary) 50%, transparent);
77
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-primary) 50%, transparent);
78
+ }
79
+
80
+ /* --- Card shadows: use light tint instead of black --- */
81
+ .ap-card {
82
+ box-shadow: 0 1px 2px hsl(var(--tint-neutral) 90% / 0.04);
83
+ }
84
+
85
+ .ap-card:hover {
86
+ box-shadow: 0 2px 8px hsl(var(--tint-neutral) 90% / 0.06);
87
+ }
88
+
89
+ /* --- Tab badge federated: soften purple --- */
90
+ .ap-tab__badge--federated {
91
+ color: var(--color-purple90);
92
+ background: color-mix(in srgb, var(--color-purple45) 18%, transparent);
93
+ }
94
+ }
@@ -0,0 +1,530 @@
1
+ /* ==========================================================================
2
+ Explore Page
3
+ ========================================================================== */
4
+
5
+ .ap-explore-header {
6
+ margin-bottom: var(--space-m);
7
+ }
8
+
9
+ .ap-explore-header__title {
10
+ font-size: var(--font-size-xl);
11
+ margin: 0 0 var(--space-xs);
12
+ }
13
+
14
+ .ap-explore-header__desc {
15
+ color: var(--color-on-offset);
16
+ font-size: var(--font-size-s);
17
+ margin: 0;
18
+ }
19
+
20
+ .ap-explore-form {
21
+ background: var(--color-offset);
22
+ border: var(--border-width-thin) solid var(--color-outline);
23
+ border-radius: var(--border-radius-small);
24
+ margin-bottom: var(--space-m);
25
+ padding: var(--space-m);
26
+ }
27
+
28
+ .ap-explore-form__row {
29
+ align-items: center;
30
+ display: flex;
31
+ gap: var(--space-s);
32
+ flex-wrap: wrap;
33
+ }
34
+
35
+ .ap-explore-form__input {
36
+ border: var(--border-width-thin) solid var(--color-outline);
37
+ border-radius: var(--border-radius-small);
38
+ box-sizing: border-box;
39
+ font-size: var(--font-size-m);
40
+ min-width: 0;
41
+ padding: var(--space-xs) var(--space-s);
42
+ width: 100%;
43
+ }
44
+
45
+ .ap-explore-form__scope {
46
+ display: flex;
47
+ gap: var(--space-s);
48
+ }
49
+
50
+ .ap-explore-form__scope-label {
51
+ align-items: center;
52
+ cursor: pointer;
53
+ display: flex;
54
+ font-size: var(--font-size-s);
55
+ gap: var(--space-xs);
56
+ }
57
+
58
+ .ap-explore-form__btn {
59
+ background: var(--color-primary);
60
+ border: none;
61
+ border-radius: var(--border-radius-small);
62
+ color: var(--color-on-primary);
63
+ cursor: pointer;
64
+ font-size: var(--font-size-s);
65
+ padding: var(--space-xs) var(--space-m);
66
+ white-space: nowrap;
67
+ }
68
+
69
+ .ap-explore-form__btn:hover {
70
+ opacity: 0.85;
71
+ }
72
+
73
+ .ap-explore-error {
74
+ background: color-mix(in srgb, var(--color-error) 10%, transparent);
75
+ border: var(--border-width-thin) solid var(--color-error);
76
+ border-radius: var(--border-radius-small);
77
+ color: var(--color-error);
78
+ margin-bottom: var(--space-m);
79
+ padding: var(--space-s) var(--space-m);
80
+ }
81
+
82
+ @media (max-width: 640px) {
83
+ .ap-explore-form__row {
84
+ flex-direction: column;
85
+ align-items: stretch;
86
+ }
87
+
88
+ .ap-explore-form__btn {
89
+ width: 100%;
90
+ }
91
+ }
92
+
93
+ /* ---------- Autocomplete dropdown ---------- */
94
+
95
+ .ap-explore-autocomplete {
96
+ flex: 1;
97
+ min-width: 0;
98
+ position: relative;
99
+ }
100
+
101
+ .ap-explore-autocomplete__dropdown {
102
+ background: var(--color-background);
103
+ border: var(--border-width-thin) solid var(--color-outline);
104
+ border-radius: var(--border-radius-small);
105
+ box-shadow: 0 4px 12px hsl(var(--tint-neutral) 10% / 0.15);
106
+ left: 0;
107
+ max-height: 320px;
108
+ overflow-y: auto;
109
+ position: absolute;
110
+ right: 0;
111
+ top: 100%;
112
+ z-index: 100;
113
+ }
114
+
115
+ .ap-explore-autocomplete__item {
116
+ align-items: center;
117
+ background: none;
118
+ border: none;
119
+ color: var(--color-on-background);
120
+ cursor: pointer;
121
+ display: flex;
122
+ font-family: inherit;
123
+ font-size: var(--font-size-s);
124
+ gap: var(--space-s);
125
+ padding: var(--space-s) var(--space-m);
126
+ text-align: left;
127
+ width: 100%;
128
+ }
129
+
130
+ .ap-explore-autocomplete__item:hover,
131
+ .ap-explore-autocomplete__item--highlighted {
132
+ background: var(--color-offset);
133
+ }
134
+
135
+ .ap-explore-autocomplete__domain {
136
+ flex-shrink: 0;
137
+ font-weight: 600;
138
+ }
139
+
140
+ .ap-explore-autocomplete__meta {
141
+ color: var(--color-on-offset);
142
+ display: flex;
143
+ flex: 1;
144
+ gap: var(--space-xs);
145
+ min-width: 0;
146
+ }
147
+
148
+ .ap-explore-autocomplete__software {
149
+ background: color-mix(in srgb, var(--color-primary) 12%, transparent);
150
+ border-radius: var(--border-radius-small);
151
+ font-size: var(--font-size-xs);
152
+ padding: 1px 6px;
153
+ white-space: nowrap;
154
+ }
155
+
156
+ .ap-explore-autocomplete__mau {
157
+ font-size: var(--font-size-xs);
158
+ white-space: nowrap;
159
+ }
160
+
161
+ .ap-explore-autocomplete__status {
162
+ flex-shrink: 0;
163
+ font-size: var(--font-size-s);
164
+ }
165
+
166
+ .ap-explore-autocomplete__checking {
167
+ opacity: 0.5;
168
+ }
169
+
170
+ /* ---------- Popular accounts autocomplete ---------- */
171
+
172
+ .ap-lookup-autocomplete {
173
+ flex: 1;
174
+ min-width: 0;
175
+ position: relative;
176
+ }
177
+
178
+ .ap-lookup-autocomplete__dropdown {
179
+ background: var(--color-background);
180
+ border: var(--border-width-thin) solid var(--color-outline);
181
+ border-radius: var(--border-radius-small);
182
+ box-shadow: 0 4px 12px hsl(var(--tint-neutral) 10% / 0.15);
183
+ left: 0;
184
+ max-height: 320px;
185
+ overflow-y: auto;
186
+ position: absolute;
187
+ right: 0;
188
+ top: 100%;
189
+ z-index: 100;
190
+ }
191
+
192
+ .ap-lookup-autocomplete__item {
193
+ align-items: center;
194
+ background: none;
195
+ border: none;
196
+ color: var(--color-on-background);
197
+ cursor: pointer;
198
+ display: flex;
199
+ font-family: inherit;
200
+ font-size: var(--font-size-s);
201
+ gap: var(--space-s);
202
+ padding: var(--space-s) var(--space-m);
203
+ text-align: left;
204
+ width: 100%;
205
+ }
206
+
207
+ .ap-lookup-autocomplete__item:hover,
208
+ .ap-lookup-autocomplete__item--highlighted {
209
+ background: var(--color-offset);
210
+ }
211
+
212
+ .ap-lookup-autocomplete__avatar {
213
+ border-radius: 50%;
214
+ flex-shrink: 0;
215
+ height: 28px;
216
+ object-fit: cover;
217
+ width: 28px;
218
+ }
219
+
220
+ .ap-lookup-autocomplete__info {
221
+ display: flex;
222
+ flex: 1;
223
+ flex-direction: column;
224
+ min-width: 0;
225
+ }
226
+
227
+ .ap-lookup-autocomplete__name {
228
+ font-weight: 600;
229
+ overflow: hidden;
230
+ text-overflow: ellipsis;
231
+ white-space: nowrap;
232
+ }
233
+
234
+ .ap-lookup-autocomplete__handle {
235
+ color: var(--color-on-offset);
236
+ font-size: var(--font-size-xs);
237
+ overflow: hidden;
238
+ text-overflow: ellipsis;
239
+ white-space: nowrap;
240
+ }
241
+
242
+ .ap-lookup-autocomplete__followers {
243
+ color: var(--color-on-offset);
244
+ flex-shrink: 0;
245
+ font-size: var(--font-size-xs);
246
+ white-space: nowrap;
247
+ }
248
+
249
+ /* ==========================================================================
250
+ Explore: Tabbed Design
251
+ ========================================================================== */
252
+
253
+ /* Tab bar wrapper: enables position:relative for fade gradient overlay */
254
+ .ap-explore-tabs-container {
255
+ position: relative;
256
+ }
257
+
258
+ /* Tab bar with right-edge fade to indicate horizontal overflow */
259
+ .ap-explore-tabs-nav {
260
+ padding-right: var(--space-l);
261
+ position: relative;
262
+ }
263
+
264
+ .ap-explore-tabs-nav::after {
265
+ background: linear-gradient(to right, transparent, var(--color-background) 80%);
266
+ content: "";
267
+ height: 100%;
268
+ pointer-events: none;
269
+ position: absolute;
270
+ right: 0;
271
+ top: 0;
272
+ width: 40px;
273
+ }
274
+
275
+ /* Tab wrapper: holds tab button + reorder/close controls together */
276
+ .ap-tab-wrapper {
277
+ align-items: stretch;
278
+ display: inline-flex;
279
+ position: relative;
280
+ }
281
+
282
+ /* Show controls on hover or when the tab is active */
283
+ .ap-tab-controls {
284
+ align-items: center;
285
+ display: none;
286
+ gap: 1px;
287
+ }
288
+
289
+ .ap-tab-wrapper:hover .ap-tab-controls,
290
+ .ap-tab-wrapper:focus-within .ap-tab-controls {
291
+ display: flex;
292
+ }
293
+
294
+ /* Individual control buttons (↑ ↓ ×) */
295
+ .ap-tab-control {
296
+ background: none;
297
+ border: none;
298
+ color: var(--color-on-offset);
299
+ cursor: pointer;
300
+ font-size: var(--font-size-xs);
301
+ line-height: 1;
302
+ padding: 2px 4px;
303
+ }
304
+
305
+ .ap-tab-control:hover {
306
+ color: var(--color-on-background);
307
+ }
308
+
309
+ .ap-tab-control:disabled {
310
+ cursor: default;
311
+ opacity: 0.3;
312
+ }
313
+
314
+ .ap-tab-control--remove {
315
+ color: var(--color-on-offset);
316
+ font-size: var(--font-size-s);
317
+ }
318
+
319
+ .ap-tab-control--remove:hover {
320
+ color: var(--color-error);
321
+ }
322
+
323
+ /* Truncate long domain names in tab labels */
324
+ .ap-tab__label {
325
+ display: inline-block;
326
+ max-width: 150px;
327
+ overflow: hidden;
328
+ text-overflow: ellipsis;
329
+ white-space: nowrap;
330
+ }
331
+
332
+ /* Scope badges on instance tabs */
333
+ .ap-tab__badge {
334
+ border-radius: var(--border-radius-small);
335
+ font-size: 0.65em;
336
+ font-weight: 700;
337
+ letter-spacing: 0.02em;
338
+ margin-left: var(--space-xs);
339
+ padding: 1px 4px;
340
+ text-transform: uppercase;
341
+ vertical-align: middle;
342
+ }
343
+
344
+ .ap-tab__badge--local {
345
+ background: color-mix(in srgb, var(--color-primary) 15%, transparent);
346
+ color: var(--color-primary-on-background);
347
+ }
348
+
349
+ .ap-tab__badge--federated {
350
+ background: color-mix(in srgb, var(--color-purple45) 15%, transparent);
351
+ color: var(--color-purple45);
352
+ }
353
+
354
+ /* +# button for adding hashtag tabs */
355
+ .ap-tab--add {
356
+ font-family: monospace;
357
+ font-weight: 700;
358
+ letter-spacing: -0.05em;
359
+ }
360
+
361
+ /* Inline hashtag form that appears when +# is clicked */
362
+ .ap-tab-add-hashtag {
363
+ align-items: center;
364
+ display: inline-flex;
365
+ gap: var(--space-xs);
366
+ }
367
+
368
+ .ap-tab-hashtag-form {
369
+ align-items: center;
370
+ display: flex;
371
+ gap: var(--space-xs);
372
+ }
373
+
374
+ .ap-tab-hashtag-form__prefix {
375
+ color: var(--color-on-offset);
376
+ font-weight: 600;
377
+ }
378
+
379
+ .ap-tab-hashtag-form__input {
380
+ border: var(--border-width-thin) solid var(--color-outline);
381
+ border-radius: var(--border-radius-small);
382
+ font-family: inherit;
383
+ font-size: var(--font-size-s);
384
+ padding: 2px var(--space-s);
385
+ width: 8em;
386
+ }
387
+
388
+ .ap-tab-hashtag-form__input:focus {
389
+ border-color: var(--color-primary);
390
+ outline: 2px solid var(--color-primary);
391
+ outline-offset: -1px;
392
+ }
393
+
394
+ .ap-tab-hashtag-form__btn {
395
+ background: var(--color-primary);
396
+ border: none;
397
+ border-radius: var(--border-radius-small);
398
+ color: var(--color-on-primary);
399
+ cursor: pointer;
400
+ font-family: inherit;
401
+ font-size: var(--font-size-s);
402
+ padding: 2px var(--space-s);
403
+ white-space: nowrap;
404
+ }
405
+
406
+ .ap-tab-hashtag-form__btn:hover {
407
+ opacity: 0.85;
408
+ }
409
+
410
+ /* "Pin as tab" button in search results area */
411
+ .ap-explore-pin-bar {
412
+ margin-bottom: var(--space-s);
413
+ }
414
+
415
+ .ap-explore-pin-btn {
416
+ background: none;
417
+ border: var(--border-width-thin) solid var(--color-primary-on-background);
418
+ border-radius: var(--border-radius-small);
419
+ color: var(--color-primary-on-background);
420
+ cursor: pointer;
421
+ font-family: inherit;
422
+ font-size: var(--font-size-s);
423
+ padding: var(--space-xs) var(--space-m);
424
+ }
425
+
426
+ .ap-explore-pin-btn:hover {
427
+ background: color-mix(in srgb, var(--color-primary) 10%, transparent);
428
+ }
429
+
430
+ .ap-explore-pin-btn:disabled {
431
+ cursor: default;
432
+ opacity: 0.6;
433
+ }
434
+
435
+ /* Hashtag form row inside the search form */
436
+ .ap-explore-form__hashtag-row {
437
+ align-items: center;
438
+ display: flex;
439
+ flex-wrap: wrap;
440
+ gap: var(--space-xs);
441
+ margin-top: var(--space-s);
442
+ }
443
+
444
+ .ap-explore-form__hashtag-label {
445
+ color: var(--color-on-offset);
446
+ font-size: var(--font-size-s);
447
+ white-space: nowrap;
448
+ }
449
+
450
+ .ap-explore-form__hashtag-prefix {
451
+ color: var(--color-on-offset);
452
+ font-weight: 600;
453
+ }
454
+
455
+ .ap-explore-form__hashtag-hint {
456
+ color: var(--color-on-offset);
457
+ font-size: var(--font-size-xs);
458
+ flex-basis: 100%;
459
+ }
460
+
461
+ .ap-explore-form__input--hashtag {
462
+ max-width: 200px;
463
+ width: auto;
464
+ }
465
+
466
+ /* Tab panel containers */
467
+ .ap-explore-instance-panel,
468
+ .ap-explore-hashtag-panel {
469
+ min-height: 120px;
470
+ }
471
+
472
+ /* Loading state */
473
+ .ap-explore-tab-loading {
474
+ align-items: center;
475
+ color: var(--color-on-offset);
476
+ display: flex;
477
+ justify-content: center;
478
+ padding: var(--space-xl);
479
+ }
480
+
481
+ .ap-explore-tab-loading--more {
482
+ padding-block: var(--space-m);
483
+ }
484
+
485
+ .ap-explore-tab-loading__text {
486
+ font-size: var(--font-size-s);
487
+ }
488
+
489
+ /* Error state */
490
+ .ap-explore-tab-error {
491
+ align-items: center;
492
+ display: flex;
493
+ flex-direction: column;
494
+ gap: var(--space-s);
495
+ padding: var(--space-xl);
496
+ }
497
+
498
+ .ap-explore-tab-error__message {
499
+ color: var(--color-error);
500
+ font-size: var(--font-size-s);
501
+ margin: 0;
502
+ }
503
+
504
+ .ap-explore-tab-error__retry {
505
+ background: none;
506
+ border: var(--border-width-thin) solid var(--color-primary-on-background);
507
+ border-radius: var(--border-radius-small);
508
+ color: var(--color-primary-on-background);
509
+ cursor: pointer;
510
+ font-size: var(--font-size-s);
511
+ padding: var(--space-xs) var(--space-s);
512
+ }
513
+
514
+ .ap-explore-tab-error__retry:hover {
515
+ background: color-mix(in srgb, var(--color-primary) 10%, transparent);
516
+ }
517
+
518
+ /* Empty state */
519
+ .ap-explore-tab-empty {
520
+ color: var(--color-on-offset);
521
+ font-size: var(--font-size-s);
522
+ padding: var(--space-xl);
523
+ text-align: center;
524
+ }
525
+
526
+ /* Infinite scroll sentinel — zero height, invisible */
527
+ .ap-tab-sentinel {
528
+ height: 1px;
529
+ visibility: hidden;
530
+ }