@rmdes/indiekit-endpoint-activitypub 3.8.7 → 3.9.1

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 +574 -584
  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,144 @@
1
+ /**
2
+ * ActivityPub Reader Styles
3
+ * Card-based layout inspired by Phanpy/Elk
4
+ * Uses Indiekit CSS custom properties for automatic dark mode support
5
+ */
6
+
7
+ /* ==========================================================================
8
+ Breadcrumb Navigation
9
+ ========================================================================== */
10
+
11
+ .ap-breadcrumb {
12
+ display: flex;
13
+ align-items: center;
14
+ gap: var(--space-xs);
15
+ margin-bottom: var(--space-m);
16
+ font-size: var(--font-size-s);
17
+ color: var(--color-on-offset);
18
+ }
19
+
20
+ .ap-breadcrumb a {
21
+ color: var(--color-primary-on-background);
22
+ text-decoration: none;
23
+ }
24
+
25
+ .ap-breadcrumb a:hover {
26
+ text-decoration: underline;
27
+ }
28
+
29
+ .ap-breadcrumb__separator {
30
+ color: var(--color-on-offset);
31
+ }
32
+
33
+ .ap-breadcrumb__current {
34
+ color: var(--color-on-background);
35
+ font-weight: 600;
36
+ }
37
+
38
+ /* ==========================================================================
39
+ Fediverse Lookup
40
+ ========================================================================== */
41
+
42
+ .ap-lookup {
43
+ display: flex;
44
+ gap: var(--space-xs);
45
+ margin-bottom: var(--space-m);
46
+ }
47
+
48
+ .ap-lookup__input {
49
+ border: var(--border-width-thin) solid var(--color-outline);
50
+ border-radius: var(--border-radius-small);
51
+ background: var(--color-offset);
52
+ box-sizing: border-box;
53
+ color: var(--color-on-background);
54
+ font-family: inherit;
55
+ font-size: var(--font-size-m);
56
+ padding: var(--space-s) var(--space-m);
57
+ width: 100%;
58
+ }
59
+
60
+ .ap-lookup__input::placeholder {
61
+ color: var(--color-on-offset);
62
+ }
63
+
64
+ .ap-lookup__input:focus {
65
+ outline: 2px solid var(--color-primary);
66
+ outline-offset: -1px;
67
+ border-color: var(--color-primary);
68
+ }
69
+
70
+ .ap-lookup__btn {
71
+ padding: var(--space-s) var(--space-m);
72
+ border: var(--border-width-thin) solid var(--color-primary);
73
+ border-radius: var(--border-radius-small);
74
+ background: var(--color-primary);
75
+ color: var(--color-on-primary);
76
+ font-size: var(--font-size-m);
77
+ font-family: inherit;
78
+ font-weight: 600;
79
+ cursor: pointer;
80
+ white-space: nowrap;
81
+ }
82
+
83
+ .ap-lookup__btn:hover {
84
+ opacity: 0.9;
85
+ }
86
+
87
+ /* ==========================================================================
88
+ Tab Navigation
89
+ ========================================================================== */
90
+
91
+ .ap-tabs {
92
+ border-bottom: var(--border-width-thin) solid var(--color-outline);
93
+ display: flex;
94
+ gap: var(--space-xs);
95
+ margin-bottom: var(--space-m);
96
+ overflow-x: auto;
97
+ -webkit-overflow-scrolling: touch;
98
+ }
99
+
100
+ .ap-tab {
101
+ border-bottom: var(--border-width-thick) solid transparent;
102
+ color: var(--color-on-offset);
103
+ font-size: var(--font-size-m);
104
+ padding: var(--space-s) var(--space-m);
105
+ text-decoration: none;
106
+ transition:
107
+ color 0.2s ease,
108
+ border-color 0.2s ease;
109
+ white-space: nowrap;
110
+ }
111
+
112
+ .ap-tab:hover {
113
+ color: var(--color-on-background);
114
+ }
115
+
116
+ .ap-tab--active {
117
+ border-bottom-color: var(--color-primary);
118
+ color: var(--color-primary-on-background);
119
+ font-weight: 600;
120
+ }
121
+
122
+ .ap-tab__count {
123
+ background: var(--color-offset-variant);
124
+ border-radius: var(--border-radius-large);
125
+ font-size: var(--font-size-xs);
126
+ font-weight: 600;
127
+ margin-left: var(--space-xs);
128
+ padding: 1px 6px;
129
+ }
130
+
131
+ .ap-tab--active .ap-tab__count {
132
+ background: var(--color-primary);
133
+ color: var(--color-on-primary, var(--color-neutral99));
134
+ }
135
+
136
+ /* ==========================================================================
137
+ Timeline Layout
138
+ ========================================================================== */
139
+
140
+ .ap-timeline {
141
+ display: flex;
142
+ flex-direction: column;
143
+ gap: var(--space-m);
144
+ }
@@ -0,0 +1,377 @@
1
+ /* ==========================================================================
2
+ Item Card — Base
3
+ ========================================================================== */
4
+
5
+ .ap-card {
6
+ background: var(--color-offset);
7
+ border: var(--border-width-thin) solid var(--color-outline);
8
+ border-left: 3px solid var(--color-outline);
9
+ border-radius: var(--border-radius-small);
10
+ overflow: hidden;
11
+ padding: var(--space-m);
12
+ box-shadow: 0 1px 2px hsl(var(--tint-neutral) 10% / 0.04);
13
+ transition:
14
+ box-shadow 0.2s ease,
15
+ border-color 0.2s ease;
16
+ }
17
+
18
+ .ap-card:hover {
19
+ border-color: var(--color-outline-variant);
20
+ border-left-color: var(--color-outline-variant);
21
+ box-shadow: 0 2px 8px hsl(var(--tint-neutral) 10% / 0.08);
22
+ }
23
+
24
+ /* ==========================================================================
25
+ Item Card — Post Type Differentiation
26
+ ========================================================================== */
27
+
28
+ /* Notes: default purple-ish accent (the most common type) */
29
+ .ap-card--note {
30
+ border-left-color: var(--color-purple45);
31
+ }
32
+
33
+ .ap-card--note:hover {
34
+ border-left-color: var(--color-purple45);
35
+ }
36
+
37
+ /* Articles: green accent (long-form content stands out) */
38
+ .ap-card--article {
39
+ border-left-color: var(--color-green50);
40
+ }
41
+
42
+ .ap-card--article:hover {
43
+ border-left-color: var(--color-green50);
44
+ }
45
+
46
+ /* Boosts: yellow accent (shared content) */
47
+ .ap-card--boost {
48
+ border-left-color: var(--color-yellow50);
49
+ }
50
+
51
+ .ap-card--boost:hover {
52
+ border-left-color: var(--color-yellow50);
53
+ }
54
+
55
+ /* Replies: blue accent (via primary color) */
56
+ .ap-card--reply {
57
+ border-left-color: var(--color-primary);
58
+ }
59
+
60
+ .ap-card--reply:hover {
61
+ border-left-color: var(--color-primary);
62
+ }
63
+
64
+ /* ==========================================================================
65
+ Boost Header
66
+ ========================================================================== */
67
+
68
+ .ap-card__boost {
69
+ color: var(--color-on-offset);
70
+ font-size: var(--font-size-s);
71
+ margin-bottom: var(--space-s);
72
+ padding-bottom: var(--space-xs);
73
+ }
74
+
75
+ .ap-card__boost a {
76
+ color: var(--color-on-offset);
77
+ font-weight: 600;
78
+ text-decoration: none;
79
+ }
80
+
81
+ .ap-card__boost a:hover {
82
+ color: var(--color-on-background);
83
+ text-decoration: underline;
84
+ }
85
+
86
+ /* ==========================================================================
87
+ Reply Context
88
+ ========================================================================== */
89
+
90
+ .ap-card__reply-to {
91
+ color: var(--color-on-offset);
92
+ font-size: var(--font-size-s);
93
+ margin-bottom: var(--space-s);
94
+ overflow: hidden;
95
+ text-overflow: ellipsis;
96
+ white-space: nowrap;
97
+ }
98
+
99
+ .ap-card__reply-to a {
100
+ color: var(--color-primary-on-background);
101
+ text-decoration: none;
102
+ }
103
+
104
+ .ap-card__reply-to a:hover {
105
+ text-decoration: underline;
106
+ }
107
+
108
+ /* ==========================================================================
109
+ Author Header
110
+ ========================================================================== */
111
+
112
+ .ap-card__author {
113
+ align-items: center;
114
+ display: flex;
115
+ gap: var(--space-s);
116
+ margin-bottom: var(--space-s);
117
+ }
118
+
119
+ .ap-card__avatar-wrap {
120
+ flex-shrink: 0;
121
+ height: 44px;
122
+ position: relative;
123
+ width: 44px;
124
+ }
125
+
126
+ .ap-card__avatar {
127
+ border: var(--border-width-thin) solid var(--color-outline);
128
+ border-radius: 50%;
129
+ height: 44px;
130
+ object-fit: cover;
131
+ width: 44px;
132
+ }
133
+
134
+ .ap-card__avatar-wrap > img {
135
+ position: absolute;
136
+ inset: 0;
137
+ z-index: 1;
138
+ }
139
+
140
+ .ap-card__avatar--default {
141
+ align-items: center;
142
+ background: var(--color-offset-variant);
143
+ color: var(--color-on-offset);
144
+ display: inline-flex;
145
+ font-size: 1.1em;
146
+ font-weight: 600;
147
+ justify-content: center;
148
+ }
149
+
150
+ .ap-card__author-info {
151
+ display: flex;
152
+ flex-direction: column;
153
+ flex: 1;
154
+ gap: 1px;
155
+ min-width: 0;
156
+ }
157
+
158
+ .ap-card__author-name {
159
+ font-size: 0.95em;
160
+ font-weight: 600;
161
+ overflow: hidden;
162
+ text-overflow: ellipsis;
163
+ white-space: nowrap;
164
+ }
165
+
166
+ .ap-card__author-name a {
167
+ color: inherit;
168
+ text-decoration: none;
169
+ }
170
+
171
+ .ap-card__author-name a:hover {
172
+ text-decoration: underline;
173
+ }
174
+
175
+ .ap-card__bot-badge {
176
+ display: inline-block;
177
+ font-size: 0.6rem;
178
+ font-weight: 700;
179
+ line-height: 1;
180
+ padding: 0.15em 0.35em;
181
+ margin-left: 0.3em;
182
+ border: var(--border-width-thin) solid var(--color-on-offset);
183
+ border-radius: var(--border-radius-small);
184
+ color: var(--color-on-offset);
185
+ vertical-align: middle;
186
+ text-transform: uppercase;
187
+ letter-spacing: 0.03em;
188
+ }
189
+
190
+ .ap-card__author-handle {
191
+ color: var(--color-on-offset);
192
+ font-size: var(--font-size-s);
193
+ overflow: hidden;
194
+ text-overflow: ellipsis;
195
+ white-space: nowrap;
196
+ }
197
+
198
+ .ap-card__timestamp {
199
+ color: var(--color-on-offset);
200
+ flex-shrink: 0;
201
+ font-size: var(--font-size-s);
202
+ }
203
+
204
+ .ap-card__edited {
205
+ font-size: var(--font-size-xs);
206
+ margin-left: 0.2em;
207
+ }
208
+
209
+ .ap-card__visibility {
210
+ font-size: var(--font-size-xs);
211
+ margin-left: 0.3em;
212
+ opacity: 0.7;
213
+ }
214
+
215
+ .ap-card__timestamp-link {
216
+ color: inherit;
217
+ text-decoration: none;
218
+ display: flex;
219
+ align-items: center;
220
+ gap: 0;
221
+ }
222
+
223
+ .ap-card__timestamp-link:hover {
224
+ text-decoration: underline;
225
+ color: var(--color-primary-on-background);
226
+ }
227
+
228
+ /* ==========================================================================
229
+ Post Title (Articles)
230
+ ========================================================================== */
231
+
232
+ .ap-card__title {
233
+ font-size: var(--font-size-l);
234
+ font-weight: 600;
235
+ line-height: var(--line-height-tight);
236
+ margin-bottom: var(--space-s);
237
+ }
238
+
239
+ .ap-card__title a {
240
+ color: inherit;
241
+ text-decoration: none;
242
+ }
243
+
244
+ .ap-card__title a:hover {
245
+ text-decoration: underline;
246
+ }
247
+
248
+ /* ==========================================================================
249
+ Content
250
+ ========================================================================== */
251
+
252
+ .ap-card__content {
253
+ color: var(--color-on-background);
254
+ line-height: calc(4 / 3 * 1em);
255
+ margin-bottom: var(--space-s);
256
+ overflow-wrap: break-word;
257
+ word-break: break-word;
258
+ }
259
+
260
+ .ap-card__content a {
261
+ color: var(--color-primary-on-background);
262
+ }
263
+
264
+ .ap-card__content p {
265
+ margin-bottom: var(--space-xs);
266
+ }
267
+
268
+ .ap-card__content p:last-child {
269
+ margin-bottom: 0;
270
+ }
271
+
272
+ .ap-card__content blockquote {
273
+ border-left: var(--border-width-thickest) solid var(--color-outline);
274
+ margin: var(--space-s) 0;
275
+ padding-left: var(--space-m);
276
+ }
277
+
278
+ .ap-card__content pre {
279
+ background: var(--color-offset-variant);
280
+ border-radius: var(--border-radius-small);
281
+ overflow-x: auto;
282
+ padding: var(--space-s);
283
+ }
284
+
285
+ .ap-card__content code {
286
+ background: var(--color-offset-variant);
287
+ border-radius: var(--border-radius-small);
288
+ font-size: 0.9em;
289
+ padding: 1px 4px;
290
+ }
291
+
292
+ .ap-card__content pre code {
293
+ background: none;
294
+ padding: 0;
295
+ }
296
+
297
+ .ap-card__content img {
298
+ border-radius: var(--border-radius-small);
299
+ height: auto;
300
+ max-width: 100%;
301
+ }
302
+
303
+ /* @mentions — keep inline, style as subtle links */
304
+ .ap-card__content .h-card {
305
+ display: inline;
306
+ }
307
+
308
+ .ap-card__content .h-card a,
309
+ .ap-card__content a.u-url.mention {
310
+ display: inline;
311
+ color: var(--color-on-offset);
312
+ text-decoration: none;
313
+ white-space: nowrap;
314
+ }
315
+
316
+ .ap-card__content .h-card a span,
317
+ .ap-card__content a.u-url.mention span {
318
+ display: inline;
319
+ }
320
+
321
+ .ap-card__content .h-card a:hover,
322
+ .ap-card__content a.u-url.mention:hover {
323
+ color: var(--color-primary-on-background);
324
+ text-decoration: underline;
325
+ }
326
+
327
+ /* Hashtag mentions — keep inline, subtle styling */
328
+ .ap-card__content a.mention.hashtag {
329
+ display: inline;
330
+ color: var(--color-on-offset);
331
+ text-decoration: none;
332
+ white-space: nowrap;
333
+ }
334
+
335
+ .ap-card__content a.mention.hashtag span {
336
+ display: inline;
337
+ }
338
+
339
+ .ap-card__content a.mention.hashtag:hover {
340
+ color: var(--color-primary-on-background);
341
+ text-decoration: underline;
342
+ }
343
+
344
+ /* Mastodon's invisible/ellipsis spans for long URLs */
345
+ .ap-card__content .invisible {
346
+ display: none;
347
+ }
348
+
349
+ .ap-card__content .ellipsis::after {
350
+ content: "…";
351
+ }
352
+
353
+ /* ==========================================================================
354
+ Content Warning
355
+ ========================================================================== */
356
+
357
+ .ap-card__cw {
358
+ margin-bottom: var(--space-s);
359
+ }
360
+
361
+ .ap-card__cw-toggle {
362
+ background: var(--color-offset-variant);
363
+ border: var(--border-width-thin) solid var(--color-outline);
364
+ border-radius: var(--border-radius-small);
365
+ color: var(--color-on-background);
366
+ cursor: pointer;
367
+ display: block;
368
+ font-size: var(--font-size-s);
369
+ padding: var(--space-s) var(--space-m);
370
+ text-align: left;
371
+ transition: background 0.2s ease;
372
+ width: 100%;
373
+ }
374
+
375
+ .ap-card__cw-toggle:hover {
376
+ background: var(--color-offset-variant-darker);
377
+ }
@@ -0,0 +1,169 @@
1
+ /* ==========================================================================
2
+ Compose Form
3
+ ========================================================================== */
4
+
5
+ .ap-compose__context {
6
+ background: var(--color-offset);
7
+ border-left: var(--border-width-thickest) solid var(--color-primary);
8
+ border-radius: var(--border-radius-small);
9
+ margin-bottom: var(--space-m);
10
+ padding: var(--space-m);
11
+ }
12
+
13
+ .ap-compose__context-label {
14
+ color: var(--color-on-offset);
15
+ font-size: var(--font-size-s);
16
+ margin-bottom: var(--space-xs);
17
+ }
18
+
19
+ .ap-compose__context-author a {
20
+ font-weight: 600;
21
+ text-decoration: none;
22
+ }
23
+
24
+ .ap-compose__context-text {
25
+ border: 0;
26
+ font-size: var(--font-size-s);
27
+ line-height: var(--line-height-loose);
28
+ margin: var(--space-xs) 0;
29
+ padding: 0;
30
+ }
31
+
32
+ .ap-compose__context-link {
33
+ color: var(--color-on-offset);
34
+ font-size: var(--font-size-s);
35
+ overflow: hidden;
36
+ text-overflow: ellipsis;
37
+ white-space: nowrap;
38
+ }
39
+
40
+ .ap-compose__form {
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: var(--space-m);
44
+ }
45
+
46
+ .ap-compose__editor {
47
+ position: relative;
48
+ }
49
+
50
+ .ap-compose__textarea {
51
+ background: var(--color-background);
52
+ border: var(--border-width-thick) solid var(--color-outline);
53
+ border-radius: var(--border-radius-small);
54
+ color: var(--color-on-background);
55
+ font-family: inherit;
56
+ font-size: var(--font-size-m);
57
+ line-height: var(--line-height-prose);
58
+ padding: var(--space-s);
59
+ resize: vertical;
60
+ width: 100%;
61
+ }
62
+
63
+ .ap-compose__textarea:focus {
64
+ border-color: var(--color-primary);
65
+ outline: var(--border-width-thick) solid var(--color-primary);
66
+ outline-offset: -2px;
67
+ }
68
+
69
+ .ap-compose__cw {
70
+ display: flex;
71
+ flex-direction: column;
72
+ gap: var(--space-xs);
73
+ }
74
+
75
+ .ap-compose__cw-toggle {
76
+ cursor: pointer;
77
+ display: flex;
78
+ align-items: center;
79
+ gap: var(--space-xs);
80
+ font-size: var(--font-size-s);
81
+ color: var(--color-on-offset);
82
+ }
83
+
84
+ .ap-compose__cw-input {
85
+ border: var(--border-width-thin) solid var(--color-outline);
86
+ border-radius: var(--border-radius-small);
87
+ background: var(--color-offset);
88
+ color: var(--color-on-background);
89
+ font: inherit;
90
+ font-size: var(--font-size-s);
91
+ padding: var(--space-s);
92
+ width: 100%;
93
+ }
94
+
95
+ .ap-compose__cw-input:focus {
96
+ border-color: var(--color-primary);
97
+ outline: none;
98
+ }
99
+
100
+ .ap-compose__visibility {
101
+ border: var(--border-width-thin) solid var(--color-outline);
102
+ border-radius: var(--border-radius-small);
103
+ display: flex;
104
+ flex-wrap: wrap;
105
+ gap: var(--space-s) var(--space-m);
106
+ padding: var(--space-m);
107
+ }
108
+
109
+ .ap-compose__visibility legend {
110
+ font-weight: 600;
111
+ }
112
+
113
+ .ap-compose__visibility-option {
114
+ cursor: pointer;
115
+ display: flex;
116
+ align-items: center;
117
+ gap: var(--space-xs);
118
+ font-size: var(--font-size-s);
119
+ }
120
+
121
+ .ap-compose__syndication {
122
+ border: var(--border-width-thin) solid var(--color-outline);
123
+ border-radius: var(--border-radius-small);
124
+ display: flex;
125
+ flex-direction: column;
126
+ gap: var(--space-xs);
127
+ padding: var(--space-m);
128
+ }
129
+
130
+ .ap-compose__syndication legend {
131
+ font-weight: 600;
132
+ }
133
+
134
+ .ap-compose__syndication-target {
135
+ cursor: pointer;
136
+ display: flex;
137
+ gap: var(--space-xs);
138
+ }
139
+
140
+ .ap-compose__actions {
141
+ align-items: center;
142
+ display: flex;
143
+ gap: var(--space-m);
144
+ }
145
+
146
+ .ap-compose__submit {
147
+ background: var(--color-primary);
148
+ border: 0;
149
+ border-radius: var(--border-radius-small);
150
+ color: var(--color-on-primary, var(--color-neutral99));
151
+ cursor: pointer;
152
+ font-size: var(--font-size-m);
153
+ font-weight: 600;
154
+ padding: var(--space-s) var(--space-l);
155
+ }
156
+
157
+ .ap-compose__submit:hover {
158
+ opacity: 0.9;
159
+ }
160
+
161
+ .ap-compose__cancel {
162
+ color: var(--color-on-offset);
163
+ text-decoration: none;
164
+ }
165
+
166
+ .ap-compose__cancel:hover {
167
+ color: var(--color-on-background);
168
+ text-decoration: underline;
169
+ }