@rmdes/indiekit-endpoint-activitypub 1.1.4 → 1.1.6
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.css +267 -153
- package/lib/inbox-listeners.js +8 -1
- package/lib/timeline-store.js +33 -3
- package/package.json +1 -1
- package/views/partials/ap-item-card.njk +9 -1
package/assets/reader.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ActivityPub Reader Styles
|
|
3
3
|
* Card-based layout inspired by Phanpy/Elk
|
|
4
|
-
* Uses Indiekit CSS custom properties
|
|
4
|
+
* Uses Indiekit CSS custom properties for automatic dark mode support
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/* ==========================================================================
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
========================================================================== */
|
|
10
10
|
|
|
11
11
|
.ap-tabs {
|
|
12
|
-
border-bottom:
|
|
12
|
+
border-bottom: var(--border-width-thin) solid var(--color-outline);
|
|
13
13
|
display: flex;
|
|
14
14
|
gap: var(--space-xs);
|
|
15
15
|
margin-bottom: var(--space-m);
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
.ap-tab {
|
|
21
|
-
border-bottom:
|
|
22
|
-
color: var(--color-
|
|
23
|
-
font-size: var(--font-size-
|
|
21
|
+
border-bottom: var(--border-width-thick) solid transparent;
|
|
22
|
+
color: var(--color-on-offset);
|
|
23
|
+
font-size: var(--font-size-m);
|
|
24
24
|
padding: var(--space-s) var(--space-m);
|
|
25
25
|
text-decoration: none;
|
|
26
26
|
transition:
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
.ap-tab:hover {
|
|
33
|
-
color: var(--color-
|
|
33
|
+
color: var(--color-on-background);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
.ap-tab--active {
|
|
@@ -50,13 +50,14 @@
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/* ==========================================================================
|
|
53
|
-
Item Card
|
|
53
|
+
Item Card — Base
|
|
54
54
|
========================================================================== */
|
|
55
55
|
|
|
56
56
|
.ap-card {
|
|
57
|
-
background: var(--color-
|
|
58
|
-
border:
|
|
59
|
-
border-
|
|
57
|
+
background: var(--color-offset);
|
|
58
|
+
border: var(--border-width-thin) solid var(--color-outline);
|
|
59
|
+
border-left: var(--border-width-thickest) solid var(--color-outline);
|
|
60
|
+
border-radius: var(--border-radius-small);
|
|
60
61
|
overflow: hidden;
|
|
61
62
|
padding: var(--space-m);
|
|
62
63
|
transition:
|
|
@@ -65,32 +66,79 @@
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
.ap-card:hover {
|
|
68
|
-
border-color: var(--color-
|
|
69
|
+
border-color: var(--color-outline-variant);
|
|
70
|
+
border-left-color: var(--color-outline-variant);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ==========================================================================
|
|
74
|
+
Item Card — Post Type Differentiation
|
|
75
|
+
========================================================================== */
|
|
76
|
+
|
|
77
|
+
/* Notes: default purple-ish accent (the most common type) */
|
|
78
|
+
.ap-card--note {
|
|
79
|
+
border-left-color: var(--color-purple45);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.ap-card--note:hover {
|
|
83
|
+
border-left-color: var(--color-purple45);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Articles: green accent (long-form content stands out) */
|
|
87
|
+
.ap-card--article {
|
|
88
|
+
border-left-color: var(--color-green50);
|
|
69
89
|
}
|
|
70
90
|
|
|
71
|
-
|
|
91
|
+
.ap-card--article:hover {
|
|
92
|
+
border-left-color: var(--color-green50);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Boosts: yellow accent (shared content) */
|
|
96
|
+
.ap-card--boost {
|
|
97
|
+
border-left-color: var(--color-yellow50);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.ap-card--boost:hover {
|
|
101
|
+
border-left-color: var(--color-yellow50);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Replies: blue accent (via primary color) */
|
|
105
|
+
.ap-card--reply {
|
|
106
|
+
border-left-color: var(--color-primary);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.ap-card--reply:hover {
|
|
110
|
+
border-left-color: var(--color-primary);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ==========================================================================
|
|
114
|
+
Boost Header
|
|
115
|
+
========================================================================== */
|
|
116
|
+
|
|
72
117
|
.ap-card__boost {
|
|
73
|
-
color: var(--color-
|
|
74
|
-
font-size: var(--font-size-
|
|
118
|
+
color: var(--color-on-offset);
|
|
119
|
+
font-size: var(--font-size-s);
|
|
75
120
|
margin-bottom: var(--space-s);
|
|
76
121
|
padding-bottom: var(--space-xs);
|
|
77
122
|
}
|
|
78
123
|
|
|
79
124
|
.ap-card__boost a {
|
|
80
|
-
color: var(--color-
|
|
125
|
+
color: var(--color-on-offset);
|
|
81
126
|
font-weight: 600;
|
|
82
127
|
text-decoration: none;
|
|
83
128
|
}
|
|
84
129
|
|
|
85
130
|
.ap-card__boost a:hover {
|
|
86
|
-
color: var(--color-
|
|
131
|
+
color: var(--color-on-background);
|
|
87
132
|
text-decoration: underline;
|
|
88
133
|
}
|
|
89
134
|
|
|
90
|
-
/*
|
|
135
|
+
/* ==========================================================================
|
|
136
|
+
Reply Context
|
|
137
|
+
========================================================================== */
|
|
138
|
+
|
|
91
139
|
.ap-card__reply-to {
|
|
92
|
-
color: var(--color-
|
|
93
|
-
font-size: var(--font-size-
|
|
140
|
+
color: var(--color-on-offset);
|
|
141
|
+
font-size: var(--font-size-s);
|
|
94
142
|
margin-bottom: var(--space-s);
|
|
95
143
|
overflow: hidden;
|
|
96
144
|
text-overflow: ellipsis;
|
|
@@ -106,7 +154,10 @@
|
|
|
106
154
|
text-decoration: underline;
|
|
107
155
|
}
|
|
108
156
|
|
|
109
|
-
/*
|
|
157
|
+
/* ==========================================================================
|
|
158
|
+
Author Header
|
|
159
|
+
========================================================================== */
|
|
160
|
+
|
|
110
161
|
.ap-card__author {
|
|
111
162
|
align-items: center;
|
|
112
163
|
display: flex;
|
|
@@ -115,7 +166,7 @@
|
|
|
115
166
|
}
|
|
116
167
|
|
|
117
168
|
.ap-card__avatar {
|
|
118
|
-
border:
|
|
169
|
+
border: var(--border-width-thin) solid var(--color-outline);
|
|
119
170
|
border-radius: 50%;
|
|
120
171
|
flex-shrink: 0;
|
|
121
172
|
height: 40px;
|
|
@@ -125,8 +176,8 @@
|
|
|
125
176
|
|
|
126
177
|
.ap-card__avatar--default {
|
|
127
178
|
align-items: center;
|
|
128
|
-
background: var(--color-offset);
|
|
129
|
-
color: var(--color-
|
|
179
|
+
background: var(--color-offset-variant);
|
|
180
|
+
color: var(--color-on-offset);
|
|
130
181
|
display: inline-flex;
|
|
131
182
|
font-size: 1.1em;
|
|
132
183
|
font-weight: 600;
|
|
@@ -157,24 +208,27 @@
|
|
|
157
208
|
}
|
|
158
209
|
|
|
159
210
|
.ap-card__author-handle {
|
|
160
|
-
color: var(--color-
|
|
161
|
-
font-size: var(--font-size-
|
|
211
|
+
color: var(--color-on-offset);
|
|
212
|
+
font-size: var(--font-size-s);
|
|
162
213
|
overflow: hidden;
|
|
163
214
|
text-overflow: ellipsis;
|
|
164
215
|
white-space: nowrap;
|
|
165
216
|
}
|
|
166
217
|
|
|
167
218
|
.ap-card__timestamp {
|
|
168
|
-
color: var(--color-
|
|
219
|
+
color: var(--color-on-offset);
|
|
169
220
|
flex-shrink: 0;
|
|
170
|
-
font-size: var(--font-size-
|
|
221
|
+
font-size: var(--font-size-xs);
|
|
171
222
|
}
|
|
172
223
|
|
|
173
|
-
/*
|
|
224
|
+
/* ==========================================================================
|
|
225
|
+
Post Title (Articles)
|
|
226
|
+
========================================================================== */
|
|
227
|
+
|
|
174
228
|
.ap-card__title {
|
|
175
|
-
font-size: var(--font-size-
|
|
229
|
+
font-size: var(--font-size-l);
|
|
176
230
|
font-weight: 600;
|
|
177
|
-
line-height:
|
|
231
|
+
line-height: var(--line-height-tight);
|
|
178
232
|
margin-bottom: var(--space-s);
|
|
179
233
|
}
|
|
180
234
|
|
|
@@ -187,10 +241,13 @@
|
|
|
187
241
|
text-decoration: underline;
|
|
188
242
|
}
|
|
189
243
|
|
|
190
|
-
/*
|
|
244
|
+
/* ==========================================================================
|
|
245
|
+
Content
|
|
246
|
+
========================================================================== */
|
|
247
|
+
|
|
191
248
|
.ap-card__content {
|
|
192
|
-
color: var(--color-
|
|
193
|
-
line-height:
|
|
249
|
+
color: var(--color-on-background);
|
|
250
|
+
line-height: var(--line-height-prose);
|
|
194
251
|
margin-bottom: var(--space-s);
|
|
195
252
|
overflow-wrap: break-word;
|
|
196
253
|
word-break: break-word;
|
|
@@ -209,20 +266,20 @@
|
|
|
209
266
|
}
|
|
210
267
|
|
|
211
268
|
.ap-card__content blockquote {
|
|
212
|
-
border-left:
|
|
269
|
+
border-left: var(--border-width-thickest) solid var(--color-outline);
|
|
213
270
|
margin: var(--space-s) 0;
|
|
214
271
|
padding-left: var(--space-m);
|
|
215
272
|
}
|
|
216
273
|
|
|
217
274
|
.ap-card__content pre {
|
|
218
|
-
background: var(--color-offset);
|
|
219
|
-
border-radius: var(--border-radius);
|
|
275
|
+
background: var(--color-offset-variant);
|
|
276
|
+
border-radius: var(--border-radius-small);
|
|
220
277
|
overflow-x: auto;
|
|
221
278
|
padding: var(--space-s);
|
|
222
279
|
}
|
|
223
280
|
|
|
224
281
|
.ap-card__content code {
|
|
225
|
-
background: var(--color-offset);
|
|
282
|
+
background: var(--color-offset-variant);
|
|
226
283
|
border-radius: 3px;
|
|
227
284
|
font-size: 0.9em;
|
|
228
285
|
padding: 1px 4px;
|
|
@@ -234,24 +291,61 @@
|
|
|
234
291
|
}
|
|
235
292
|
|
|
236
293
|
.ap-card__content img {
|
|
237
|
-
border-radius: var(--border-radius);
|
|
294
|
+
border-radius: var(--border-radius-small);
|
|
238
295
|
height: auto;
|
|
239
296
|
max-width: 100%;
|
|
240
297
|
}
|
|
241
298
|
|
|
242
|
-
/*
|
|
299
|
+
/* @mentions — styled as subtle pills to distinguish from prose */
|
|
300
|
+
.ap-card__content .h-card,
|
|
301
|
+
.ap-card__content a.u-url.mention {
|
|
302
|
+
color: var(--color-on-offset);
|
|
303
|
+
font-size: var(--font-size-s);
|
|
304
|
+
text-decoration: none;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.ap-card__content a.u-url.mention:hover {
|
|
308
|
+
color: var(--color-primary);
|
|
309
|
+
text-decoration: underline;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* Hashtag mentions — subtle tag styling */
|
|
313
|
+
.ap-card__content a.mention.hashtag {
|
|
314
|
+
color: var(--color-on-offset);
|
|
315
|
+
font-size: var(--font-size-s);
|
|
316
|
+
text-decoration: none;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.ap-card__content a.mention.hashtag:hover {
|
|
320
|
+
color: var(--color-primary);
|
|
321
|
+
text-decoration: underline;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Mastodon's invisible/ellipsis spans for long URLs */
|
|
325
|
+
.ap-card__content .invisible {
|
|
326
|
+
display: none;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.ap-card__content .ellipsis::after {
|
|
330
|
+
content: "…";
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* ==========================================================================
|
|
334
|
+
Content Warning
|
|
335
|
+
========================================================================== */
|
|
336
|
+
|
|
243
337
|
.ap-card__cw {
|
|
244
338
|
margin-bottom: var(--space-s);
|
|
245
339
|
}
|
|
246
340
|
|
|
247
341
|
.ap-card__cw-toggle {
|
|
248
|
-
background: var(--color-offset);
|
|
249
|
-
border:
|
|
250
|
-
border-radius: var(--border-radius);
|
|
251
|
-
color: var(--color-
|
|
342
|
+
background: var(--color-offset-variant);
|
|
343
|
+
border: var(--border-width-thin) solid var(--color-outline);
|
|
344
|
+
border-radius: var(--border-radius-small);
|
|
345
|
+
color: var(--color-on-background);
|
|
252
346
|
cursor: pointer;
|
|
253
347
|
display: block;
|
|
254
|
-
font-size: var(--font-size-
|
|
348
|
+
font-size: var(--font-size-s);
|
|
255
349
|
padding: var(--space-s) var(--space-m);
|
|
256
350
|
text-align: left;
|
|
257
351
|
transition: background 0.2s ease;
|
|
@@ -259,12 +353,15 @@
|
|
|
259
353
|
}
|
|
260
354
|
|
|
261
355
|
.ap-card__cw-toggle:hover {
|
|
262
|
-
background: var(--color-offset-
|
|
356
|
+
background: var(--color-offset-variant-darker);
|
|
263
357
|
}
|
|
264
358
|
|
|
265
|
-
/*
|
|
359
|
+
/* ==========================================================================
|
|
360
|
+
Photo Gallery
|
|
361
|
+
========================================================================== */
|
|
362
|
+
|
|
266
363
|
.ap-card__gallery {
|
|
267
|
-
border-radius: var(--border-radius);
|
|
364
|
+
border-radius: var(--border-radius-small);
|
|
268
365
|
display: grid;
|
|
269
366
|
gap: 2px;
|
|
270
367
|
margin-bottom: var(--space-s);
|
|
@@ -277,7 +374,7 @@
|
|
|
277
374
|
}
|
|
278
375
|
|
|
279
376
|
.ap-card__gallery img {
|
|
280
|
-
background: var(--color-offset);
|
|
377
|
+
background: var(--color-offset-variant);
|
|
281
378
|
display: block;
|
|
282
379
|
height: 200px;
|
|
283
380
|
object-fit: cover;
|
|
@@ -285,7 +382,7 @@
|
|
|
285
382
|
}
|
|
286
383
|
|
|
287
384
|
.ap-card__gallery-link--more::after {
|
|
288
|
-
background:
|
|
385
|
+
background: hsl(var(--tint-neutral) 10% / 0.5);
|
|
289
386
|
bottom: 0;
|
|
290
387
|
content: "";
|
|
291
388
|
left: 0;
|
|
@@ -295,7 +392,7 @@
|
|
|
295
392
|
}
|
|
296
393
|
|
|
297
394
|
.ap-card__gallery-more {
|
|
298
|
-
color:
|
|
395
|
+
color: var(--color-neutral99);
|
|
299
396
|
font-size: 1.5em;
|
|
300
397
|
font-weight: 600;
|
|
301
398
|
left: 50%;
|
|
@@ -315,12 +412,12 @@
|
|
|
315
412
|
max-height: 400px;
|
|
316
413
|
}
|
|
317
414
|
|
|
318
|
-
/* 2 photos
|
|
415
|
+
/* 2 photos — side by side */
|
|
319
416
|
.ap-card__gallery--2 {
|
|
320
417
|
grid-template-columns: 1fr 1fr;
|
|
321
418
|
}
|
|
322
419
|
|
|
323
|
-
/* 3 photos
|
|
420
|
+
/* 3 photos — one large, two small */
|
|
324
421
|
.ap-card__gallery--3 {
|
|
325
422
|
grid-template-columns: 2fr 1fr;
|
|
326
423
|
grid-template-rows: 1fr 1fr;
|
|
@@ -331,24 +428,30 @@
|
|
|
331
428
|
height: 100%;
|
|
332
429
|
}
|
|
333
430
|
|
|
334
|
-
/* 4+ photos
|
|
431
|
+
/* 4+ photos — 2x2 grid */
|
|
335
432
|
.ap-card__gallery--4 {
|
|
336
433
|
grid-template-columns: 1fr 1fr;
|
|
337
434
|
grid-template-rows: 1fr 1fr;
|
|
338
435
|
}
|
|
339
436
|
|
|
340
|
-
/*
|
|
437
|
+
/* ==========================================================================
|
|
438
|
+
Video Embed
|
|
439
|
+
========================================================================== */
|
|
440
|
+
|
|
341
441
|
.ap-card__video {
|
|
342
442
|
margin-bottom: var(--space-s);
|
|
343
443
|
}
|
|
344
444
|
|
|
345
445
|
.ap-card__video video {
|
|
346
|
-
border-radius: var(--border-radius);
|
|
446
|
+
border-radius: var(--border-radius-small);
|
|
347
447
|
max-height: 400px;
|
|
348
448
|
width: 100%;
|
|
349
449
|
}
|
|
350
450
|
|
|
351
|
-
/*
|
|
451
|
+
/* ==========================================================================
|
|
452
|
+
Audio Player
|
|
453
|
+
========================================================================== */
|
|
454
|
+
|
|
352
455
|
.ap-card__audio {
|
|
353
456
|
margin-bottom: var(--space-s);
|
|
354
457
|
}
|
|
@@ -357,7 +460,10 @@
|
|
|
357
460
|
width: 100%;
|
|
358
461
|
}
|
|
359
462
|
|
|
360
|
-
/*
|
|
463
|
+
/* ==========================================================================
|
|
464
|
+
Tags
|
|
465
|
+
========================================================================== */
|
|
466
|
+
|
|
361
467
|
.ap-card__tags {
|
|
362
468
|
display: flex;
|
|
363
469
|
flex-wrap: wrap;
|
|
@@ -366,22 +472,25 @@
|
|
|
366
472
|
}
|
|
367
473
|
|
|
368
474
|
.ap-card__tag {
|
|
369
|
-
background: var(--color-offset);
|
|
370
|
-
border-radius: var(--border-radius);
|
|
371
|
-
color: var(--color-
|
|
372
|
-
font-size: var(--font-size-
|
|
475
|
+
background: var(--color-offset-variant);
|
|
476
|
+
border-radius: var(--border-radius-large);
|
|
477
|
+
color: var(--color-on-offset);
|
|
478
|
+
font-size: var(--font-size-s);
|
|
373
479
|
padding: 2px var(--space-xs);
|
|
374
480
|
text-decoration: none;
|
|
375
481
|
}
|
|
376
482
|
|
|
377
483
|
.ap-card__tag:hover {
|
|
378
|
-
background: var(--color-offset-
|
|
379
|
-
color: var(--color-
|
|
484
|
+
background: var(--color-offset-variant-darker);
|
|
485
|
+
color: var(--color-on-background);
|
|
380
486
|
}
|
|
381
487
|
|
|
382
|
-
/*
|
|
488
|
+
/* ==========================================================================
|
|
489
|
+
Interaction Buttons
|
|
490
|
+
========================================================================== */
|
|
491
|
+
|
|
383
492
|
.ap-card__actions {
|
|
384
|
-
border-top:
|
|
493
|
+
border-top: var(--border-width-thin) solid var(--color-outline);
|
|
385
494
|
display: flex;
|
|
386
495
|
flex-wrap: wrap;
|
|
387
496
|
gap: var(--space-s);
|
|
@@ -391,12 +500,12 @@
|
|
|
391
500
|
.ap-card__action {
|
|
392
501
|
align-items: center;
|
|
393
502
|
background: transparent;
|
|
394
|
-
border:
|
|
395
|
-
border-radius: var(--border-radius);
|
|
396
|
-
color: var(--color-
|
|
503
|
+
border: var(--border-width-thin) solid var(--color-outline);
|
|
504
|
+
border-radius: var(--border-radius-small);
|
|
505
|
+
color: var(--color-on-offset);
|
|
397
506
|
cursor: pointer;
|
|
398
507
|
display: inline-flex;
|
|
399
|
-
font-size: var(--font-size-
|
|
508
|
+
font-size: var(--font-size-s);
|
|
400
509
|
gap: var(--space-xs);
|
|
401
510
|
padding: var(--space-xs) var(--space-s);
|
|
402
511
|
text-decoration: none;
|
|
@@ -404,22 +513,22 @@
|
|
|
404
513
|
}
|
|
405
514
|
|
|
406
515
|
.ap-card__action:hover {
|
|
407
|
-
background: var(--color-offset);
|
|
408
|
-
border-color: var(--color-
|
|
409
|
-
color: var(--color-
|
|
516
|
+
background: var(--color-offset-variant);
|
|
517
|
+
border-color: var(--color-outline-variant);
|
|
518
|
+
color: var(--color-on-background);
|
|
410
519
|
}
|
|
411
520
|
|
|
412
|
-
/* Active interaction states */
|
|
521
|
+
/* Active interaction states — using Indiekit's color palette */
|
|
413
522
|
.ap-card__action--like.ap-card__action--active {
|
|
414
|
-
background:
|
|
415
|
-
border-color:
|
|
416
|
-
color:
|
|
523
|
+
background: var(--color-red90);
|
|
524
|
+
border-color: var(--color-red45);
|
|
525
|
+
color: var(--color-red45);
|
|
417
526
|
}
|
|
418
527
|
|
|
419
528
|
.ap-card__action--boost.ap-card__action--active {
|
|
420
|
-
background:
|
|
421
|
-
border-color:
|
|
422
|
-
color:
|
|
529
|
+
background: var(--color-green90);
|
|
530
|
+
border-color: var(--color-green50);
|
|
531
|
+
color: var(--color-green50);
|
|
423
532
|
}
|
|
424
533
|
|
|
425
534
|
.ap-card__action:disabled {
|
|
@@ -429,8 +538,8 @@
|
|
|
429
538
|
|
|
430
539
|
/* Error message */
|
|
431
540
|
.ap-card__action-error {
|
|
432
|
-
color:
|
|
433
|
-
font-size: var(--font-size-
|
|
541
|
+
color: var(--color-error);
|
|
542
|
+
font-size: var(--font-size-s);
|
|
434
543
|
width: 100%;
|
|
435
544
|
}
|
|
436
545
|
|
|
@@ -439,7 +548,7 @@
|
|
|
439
548
|
========================================================================== */
|
|
440
549
|
|
|
441
550
|
.ap-pagination {
|
|
442
|
-
border-top:
|
|
551
|
+
border-top: var(--border-width-thin) solid var(--color-outline);
|
|
443
552
|
display: flex;
|
|
444
553
|
gap: var(--space-m);
|
|
445
554
|
justify-content: space-between;
|
|
@@ -462,15 +571,15 @@
|
|
|
462
571
|
|
|
463
572
|
.ap-compose__context {
|
|
464
573
|
background: var(--color-offset);
|
|
465
|
-
border-left:
|
|
466
|
-
border-radius: var(--border-radius);
|
|
574
|
+
border-left: var(--border-width-thickest) solid var(--color-primary);
|
|
575
|
+
border-radius: var(--border-radius-small);
|
|
467
576
|
margin-bottom: var(--space-m);
|
|
468
577
|
padding: var(--space-m);
|
|
469
578
|
}
|
|
470
579
|
|
|
471
580
|
.ap-compose__context-label {
|
|
472
|
-
color: var(--color-
|
|
473
|
-
font-size: var(--font-size-
|
|
581
|
+
color: var(--color-on-offset);
|
|
582
|
+
font-size: var(--font-size-s);
|
|
474
583
|
margin-bottom: var(--space-xs);
|
|
475
584
|
}
|
|
476
585
|
|
|
@@ -481,15 +590,15 @@
|
|
|
481
590
|
|
|
482
591
|
.ap-compose__context-text {
|
|
483
592
|
border: 0;
|
|
484
|
-
font-size: var(--font-size-
|
|
485
|
-
line-height:
|
|
593
|
+
font-size: var(--font-size-s);
|
|
594
|
+
line-height: var(--line-height-loose);
|
|
486
595
|
margin: var(--space-xs) 0;
|
|
487
596
|
padding: 0;
|
|
488
597
|
}
|
|
489
598
|
|
|
490
599
|
.ap-compose__context-link {
|
|
491
|
-
color: var(--color-
|
|
492
|
-
font-size: var(--font-size-
|
|
600
|
+
color: var(--color-on-offset);
|
|
601
|
+
font-size: var(--font-size-s);
|
|
493
602
|
overflow: hidden;
|
|
494
603
|
text-overflow: ellipsis;
|
|
495
604
|
white-space: nowrap;
|
|
@@ -502,8 +611,8 @@
|
|
|
502
611
|
}
|
|
503
612
|
|
|
504
613
|
.ap-compose__mode {
|
|
505
|
-
border:
|
|
506
|
-
border-radius: var(--border-radius);
|
|
614
|
+
border: var(--border-width-thin) solid var(--color-outline);
|
|
615
|
+
border-radius: var(--border-radius-small);
|
|
507
616
|
display: flex;
|
|
508
617
|
flex-direction: column;
|
|
509
618
|
gap: var(--space-s);
|
|
@@ -522,9 +631,9 @@
|
|
|
522
631
|
}
|
|
523
632
|
|
|
524
633
|
.ap-compose__mode-hint {
|
|
525
|
-
color: var(--color-
|
|
634
|
+
color: var(--color-on-offset);
|
|
526
635
|
display: block;
|
|
527
|
-
font-size: var(--font-size-
|
|
636
|
+
font-size: var(--font-size-s);
|
|
528
637
|
margin-left: 1.5em;
|
|
529
638
|
width: 100%;
|
|
530
639
|
}
|
|
@@ -534,11 +643,13 @@
|
|
|
534
643
|
}
|
|
535
644
|
|
|
536
645
|
.ap-compose__textarea {
|
|
537
|
-
|
|
538
|
-
border
|
|
646
|
+
background: var(--color-background);
|
|
647
|
+
border: var(--border-width-thick) solid var(--color-outline);
|
|
648
|
+
border-radius: var(--border-radius-small);
|
|
649
|
+
color: var(--color-on-background);
|
|
539
650
|
font-family: inherit;
|
|
540
|
-
font-size: var(--font-size-
|
|
541
|
-
line-height:
|
|
651
|
+
font-size: var(--font-size-m);
|
|
652
|
+
line-height: var(--line-height-prose);
|
|
542
653
|
padding: var(--space-s);
|
|
543
654
|
resize: vertical;
|
|
544
655
|
width: 100%;
|
|
@@ -546,28 +657,28 @@
|
|
|
546
657
|
|
|
547
658
|
.ap-compose__textarea:focus {
|
|
548
659
|
border-color: var(--color-primary);
|
|
549
|
-
outline:
|
|
660
|
+
outline: var(--border-width-thick) solid var(--color-primary);
|
|
550
661
|
outline-offset: -2px;
|
|
551
662
|
}
|
|
552
663
|
|
|
553
664
|
.ap-compose__counter {
|
|
554
|
-
font-size: var(--font-size-
|
|
665
|
+
font-size: var(--font-size-s);
|
|
555
666
|
padding-top: var(--space-xs);
|
|
556
667
|
text-align: right;
|
|
557
668
|
}
|
|
558
669
|
|
|
559
670
|
.ap-compose__counter--warn {
|
|
560
|
-
color:
|
|
671
|
+
color: var(--color-yellow50);
|
|
561
672
|
}
|
|
562
673
|
|
|
563
674
|
.ap-compose__counter--over {
|
|
564
|
-
color:
|
|
675
|
+
color: var(--color-error);
|
|
565
676
|
font-weight: 600;
|
|
566
677
|
}
|
|
567
678
|
|
|
568
679
|
.ap-compose__syndication {
|
|
569
|
-
border:
|
|
570
|
-
border-radius: var(--border-radius);
|
|
680
|
+
border: var(--border-width-thin) solid var(--color-outline);
|
|
681
|
+
border-radius: var(--border-radius-small);
|
|
571
682
|
display: flex;
|
|
572
683
|
flex-direction: column;
|
|
573
684
|
gap: var(--space-xs);
|
|
@@ -593,10 +704,10 @@
|
|
|
593
704
|
.ap-compose__submit {
|
|
594
705
|
background: var(--color-primary);
|
|
595
706
|
border: 0;
|
|
596
|
-
border-radius: var(--border-radius);
|
|
597
|
-
color:
|
|
707
|
+
border-radius: var(--border-radius-small);
|
|
708
|
+
color: var(--color-on-primary, var(--color-neutral99));
|
|
598
709
|
cursor: pointer;
|
|
599
|
-
font-size: var(--font-size-
|
|
710
|
+
font-size: var(--font-size-m);
|
|
600
711
|
font-weight: 600;
|
|
601
712
|
padding: var(--space-s) var(--space-l);
|
|
602
713
|
}
|
|
@@ -606,12 +717,12 @@
|
|
|
606
717
|
}
|
|
607
718
|
|
|
608
719
|
.ap-compose__cancel {
|
|
609
|
-
color: var(--color-
|
|
720
|
+
color: var(--color-on-offset);
|
|
610
721
|
text-decoration: none;
|
|
611
722
|
}
|
|
612
723
|
|
|
613
724
|
.ap-compose__cancel:hover {
|
|
614
|
-
color: var(--color-
|
|
725
|
+
color: var(--color-on-background);
|
|
615
726
|
text-decoration: underline;
|
|
616
727
|
}
|
|
617
728
|
|
|
@@ -621,17 +732,17 @@
|
|
|
621
732
|
|
|
622
733
|
.ap-notification {
|
|
623
734
|
align-items: flex-start;
|
|
624
|
-
background: var(--color-
|
|
625
|
-
border:
|
|
626
|
-
border-radius: var(--border-radius);
|
|
735
|
+
background: var(--color-offset);
|
|
736
|
+
border: var(--border-width-thin) solid var(--color-outline);
|
|
737
|
+
border-radius: var(--border-radius-small);
|
|
627
738
|
display: flex;
|
|
628
739
|
gap: var(--space-s);
|
|
629
740
|
padding: var(--space-m);
|
|
630
741
|
}
|
|
631
742
|
|
|
632
743
|
.ap-notification--unread {
|
|
633
|
-
border-color:
|
|
634
|
-
box-shadow: 0 0 8px 0
|
|
744
|
+
border-color: var(--color-yellow50);
|
|
745
|
+
box-shadow: 0 0 8px 0 hsl(var(--tint-yellow) 50% / 0.3);
|
|
635
746
|
}
|
|
636
747
|
|
|
637
748
|
.ap-notification__icon {
|
|
@@ -649,13 +760,13 @@
|
|
|
649
760
|
}
|
|
650
761
|
|
|
651
762
|
.ap-notification__action {
|
|
652
|
-
color: var(--color-
|
|
763
|
+
color: var(--color-on-offset);
|
|
653
764
|
}
|
|
654
765
|
|
|
655
766
|
.ap-notification__target {
|
|
656
|
-
color: var(--color-
|
|
767
|
+
color: var(--color-on-offset);
|
|
657
768
|
display: block;
|
|
658
|
-
font-size: var(--font-size-
|
|
769
|
+
font-size: var(--font-size-s);
|
|
659
770
|
margin-top: var(--space-xs);
|
|
660
771
|
overflow: hidden;
|
|
661
772
|
text-overflow: ellipsis;
|
|
@@ -663,17 +774,17 @@
|
|
|
663
774
|
}
|
|
664
775
|
|
|
665
776
|
.ap-notification__excerpt {
|
|
666
|
-
background: var(--color-offset);
|
|
667
|
-
border-radius: var(--border-radius);
|
|
668
|
-
font-size: var(--font-size-
|
|
777
|
+
background: var(--color-offset-variant);
|
|
778
|
+
border-radius: var(--border-radius-small);
|
|
779
|
+
font-size: var(--font-size-s);
|
|
669
780
|
margin-top: var(--space-xs);
|
|
670
781
|
padding: var(--space-xs) var(--space-s);
|
|
671
782
|
}
|
|
672
783
|
|
|
673
784
|
.ap-notification__time {
|
|
674
|
-
color: var(--color-
|
|
785
|
+
color: var(--color-on-offset);
|
|
675
786
|
flex-shrink: 0;
|
|
676
|
-
font-size: var(--font-size-
|
|
787
|
+
font-size: var(--font-size-xs);
|
|
677
788
|
}
|
|
678
789
|
|
|
679
790
|
/* ==========================================================================
|
|
@@ -681,7 +792,7 @@
|
|
|
681
792
|
========================================================================== */
|
|
682
793
|
|
|
683
794
|
.ap-profile__header {
|
|
684
|
-
border-radius: var(--border-radius);
|
|
795
|
+
border-radius: var(--border-radius-small);
|
|
685
796
|
height: 200px;
|
|
686
797
|
margin-bottom: var(--space-m);
|
|
687
798
|
overflow: hidden;
|
|
@@ -702,7 +813,7 @@
|
|
|
702
813
|
}
|
|
703
814
|
|
|
704
815
|
.ap-profile__avatar {
|
|
705
|
-
border:
|
|
816
|
+
border: var(--border-width-thickest) solid var(--color-background);
|
|
706
817
|
border-radius: 50%;
|
|
707
818
|
height: 80px;
|
|
708
819
|
object-fit: cover;
|
|
@@ -711,8 +822,8 @@
|
|
|
711
822
|
|
|
712
823
|
.ap-profile__avatar--placeholder {
|
|
713
824
|
align-items: center;
|
|
714
|
-
background: var(--color-offset);
|
|
715
|
-
color: var(--color-
|
|
825
|
+
background: var(--color-offset-variant);
|
|
826
|
+
color: var(--color-on-offset);
|
|
716
827
|
display: flex;
|
|
717
828
|
font-size: 2em;
|
|
718
829
|
font-weight: 600;
|
|
@@ -720,17 +831,17 @@
|
|
|
720
831
|
}
|
|
721
832
|
|
|
722
833
|
.ap-profile__name {
|
|
723
|
-
font-size: var(--font-size-
|
|
834
|
+
font-size: var(--font-size-xl);
|
|
724
835
|
margin-bottom: var(--space-xs);
|
|
725
836
|
}
|
|
726
837
|
|
|
727
838
|
.ap-profile__handle {
|
|
728
|
-
color: var(--color-
|
|
839
|
+
color: var(--color-on-offset);
|
|
729
840
|
margin-bottom: var(--space-s);
|
|
730
841
|
}
|
|
731
842
|
|
|
732
843
|
.ap-profile__bio {
|
|
733
|
-
line-height:
|
|
844
|
+
line-height: var(--line-height-prose);
|
|
734
845
|
margin-bottom: var(--space-s);
|
|
735
846
|
}
|
|
736
847
|
|
|
@@ -747,11 +858,11 @@
|
|
|
747
858
|
|
|
748
859
|
.ap-profile__action {
|
|
749
860
|
background: transparent;
|
|
750
|
-
border:
|
|
751
|
-
border-radius: var(--border-radius);
|
|
752
|
-
color: var(--color-
|
|
861
|
+
border: var(--border-width-thin) solid var(--color-outline);
|
|
862
|
+
border-radius: var(--border-radius-small);
|
|
863
|
+
color: var(--color-on-background);
|
|
753
864
|
cursor: pointer;
|
|
754
|
-
font-size: var(--font-size-
|
|
865
|
+
font-size: var(--font-size-s);
|
|
755
866
|
padding: var(--space-xs) var(--space-m);
|
|
756
867
|
text-decoration: none;
|
|
757
868
|
}
|
|
@@ -763,12 +874,12 @@
|
|
|
763
874
|
.ap-profile__action--follow.ap-profile__action--active {
|
|
764
875
|
background: var(--color-primary);
|
|
765
876
|
border-color: var(--color-primary);
|
|
766
|
-
color:
|
|
877
|
+
color: var(--color-on-primary, var(--color-neutral99));
|
|
767
878
|
}
|
|
768
879
|
|
|
769
880
|
.ap-profile__action--danger:hover {
|
|
770
|
-
border-color:
|
|
771
|
-
color:
|
|
881
|
+
border-color: var(--color-error);
|
|
882
|
+
color: var(--color-error);
|
|
772
883
|
}
|
|
773
884
|
|
|
774
885
|
.ap-profile__posts {
|
|
@@ -776,8 +887,8 @@
|
|
|
776
887
|
}
|
|
777
888
|
|
|
778
889
|
.ap-profile__posts h3 {
|
|
779
|
-
border-bottom:
|
|
780
|
-
font-size: var(--font-size-
|
|
890
|
+
border-bottom: var(--border-width-thin) solid var(--color-outline);
|
|
891
|
+
font-size: var(--font-size-l);
|
|
781
892
|
margin-bottom: var(--space-m);
|
|
782
893
|
padding-bottom: var(--space-s);
|
|
783
894
|
}
|
|
@@ -791,7 +902,7 @@
|
|
|
791
902
|
}
|
|
792
903
|
|
|
793
904
|
.ap-moderation__section h2 {
|
|
794
|
-
font-size: var(--font-size-
|
|
905
|
+
font-size: var(--font-size-l);
|
|
795
906
|
margin-bottom: var(--space-s);
|
|
796
907
|
}
|
|
797
908
|
|
|
@@ -803,7 +914,7 @@
|
|
|
803
914
|
|
|
804
915
|
.ap-moderation__entry {
|
|
805
916
|
align-items: center;
|
|
806
|
-
border-bottom:
|
|
917
|
+
border-bottom: var(--border-width-thin) solid var(--color-outline);
|
|
807
918
|
display: flex;
|
|
808
919
|
gap: var(--space-s);
|
|
809
920
|
justify-content: space-between;
|
|
@@ -819,18 +930,18 @@
|
|
|
819
930
|
|
|
820
931
|
.ap-moderation__remove {
|
|
821
932
|
background: transparent;
|
|
822
|
-
border:
|
|
823
|
-
border-radius: var(--border-radius);
|
|
824
|
-
color: var(--color-
|
|
933
|
+
border: var(--border-width-thin) solid var(--color-outline);
|
|
934
|
+
border-radius: var(--border-radius-small);
|
|
935
|
+
color: var(--color-on-offset);
|
|
825
936
|
cursor: pointer;
|
|
826
937
|
flex-shrink: 0;
|
|
827
|
-
font-size: var(--font-size-
|
|
938
|
+
font-size: var(--font-size-s);
|
|
828
939
|
padding: var(--space-xs) var(--space-s);
|
|
829
940
|
}
|
|
830
941
|
|
|
831
942
|
.ap-moderation__remove:hover {
|
|
832
|
-
border-color:
|
|
833
|
-
color:
|
|
943
|
+
border-color: var(--color-error);
|
|
944
|
+
color: var(--color-error);
|
|
834
945
|
}
|
|
835
946
|
|
|
836
947
|
.ap-moderation__add-form {
|
|
@@ -839,24 +950,27 @@
|
|
|
839
950
|
}
|
|
840
951
|
|
|
841
952
|
.ap-moderation__input {
|
|
842
|
-
|
|
843
|
-
border
|
|
953
|
+
background: var(--color-background);
|
|
954
|
+
border: var(--border-width-thick) solid var(--color-outline);
|
|
955
|
+
border-radius: var(--border-radius-small);
|
|
956
|
+
color: var(--color-on-background);
|
|
844
957
|
flex: 1;
|
|
845
|
-
font-size: var(--font-size-
|
|
958
|
+
font-size: var(--font-size-m);
|
|
846
959
|
padding: var(--space-xs) var(--space-s);
|
|
847
960
|
}
|
|
848
961
|
|
|
849
962
|
.ap-moderation__add-btn {
|
|
850
963
|
background: var(--color-offset);
|
|
851
|
-
border:
|
|
852
|
-
border-radius: var(--border-radius);
|
|
964
|
+
border: var(--border-width-thin) solid var(--color-outline);
|
|
965
|
+
border-radius: var(--border-radius-small);
|
|
966
|
+
color: var(--color-on-background);
|
|
853
967
|
cursor: pointer;
|
|
854
|
-
font-size: var(--font-size-
|
|
968
|
+
font-size: var(--font-size-m);
|
|
855
969
|
padding: var(--space-xs) var(--space-m);
|
|
856
970
|
}
|
|
857
971
|
|
|
858
972
|
.ap-moderation__add-btn:hover {
|
|
859
|
-
background: var(--color-offset-
|
|
973
|
+
background: var(--color-offset-variant);
|
|
860
974
|
}
|
|
861
975
|
|
|
862
976
|
/* ==========================================================================
|
package/lib/inbox-listeners.js
CHANGED
|
@@ -321,6 +321,11 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
321
321
|
const object = await announce.getObject();
|
|
322
322
|
if (!object) return;
|
|
323
323
|
|
|
324
|
+
// Skip non-content objects (Lemmy/PieFed like/create activities
|
|
325
|
+
// that resolve to activity IDs instead of actual Note/Article posts)
|
|
326
|
+
const hasContent = object.content?.toString() || object.name?.toString();
|
|
327
|
+
if (!hasContent) return;
|
|
328
|
+
|
|
324
329
|
// Get booster actor info
|
|
325
330
|
const boosterActor = await announce.getActor();
|
|
326
331
|
const boosterInfo = await extractActorInfo(boosterActor);
|
|
@@ -446,7 +451,9 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
446
451
|
const following = await collections.ap_following.findOne({ actorUrl });
|
|
447
452
|
if (following) {
|
|
448
453
|
try {
|
|
449
|
-
const timelineItem = await extractObjectData(object
|
|
454
|
+
const timelineItem = await extractObjectData(object, {
|
|
455
|
+
actorFallback: actorObj,
|
|
456
|
+
});
|
|
450
457
|
await addTimelineItem(collections, timelineItem);
|
|
451
458
|
} catch (error) {
|
|
452
459
|
// Log extraction errors but don't fail the entire handler
|
package/lib/timeline-store.js
CHANGED
|
@@ -87,6 +87,7 @@ export async function extractActorInfo(actor) {
|
|
|
87
87
|
* @param {object} options - Extraction options
|
|
88
88
|
* @param {object} [options.boostedBy] - Actor info for boosts
|
|
89
89
|
* @param {Date} [options.boostedAt] - Boost timestamp
|
|
90
|
+
* @param {object} [options.actorFallback] - Fedify actor to use when object.getAttributedTo() fails
|
|
90
91
|
* @returns {Promise<object>} Timeline item data
|
|
91
92
|
*/
|
|
92
93
|
export async function extractObjectData(object, options = {}) {
|
|
@@ -127,7 +128,7 @@ export async function extractObjectData(object, options = {}) {
|
|
|
127
128
|
? String(object.published)
|
|
128
129
|
: new Date().toISOString();
|
|
129
130
|
|
|
130
|
-
// Extract author —
|
|
131
|
+
// Extract author — try multiple strategies in order of reliability
|
|
131
132
|
let authorObj = null;
|
|
132
133
|
try {
|
|
133
134
|
if (typeof object.getAttributedTo === "function") {
|
|
@@ -135,10 +136,39 @@ export async function extractObjectData(object, options = {}) {
|
|
|
135
136
|
authorObj = Array.isArray(attr) ? attr[0] : attr;
|
|
136
137
|
}
|
|
137
138
|
} catch {
|
|
138
|
-
//
|
|
139
|
+
// getAttributedTo() failed (Authorized Fetch, unreachable, etc.)
|
|
140
|
+
}
|
|
141
|
+
// If getAttributedTo() returned nothing, use the actor from the wrapping activity
|
|
142
|
+
if (!authorObj && options.actorFallback) {
|
|
143
|
+
authorObj = options.actorFallback;
|
|
144
|
+
}
|
|
145
|
+
// Try direct property access for plain objects
|
|
146
|
+
if (!authorObj) {
|
|
139
147
|
authorObj = object.attribution || object.attributedTo || null;
|
|
140
148
|
}
|
|
141
|
-
|
|
149
|
+
|
|
150
|
+
let author;
|
|
151
|
+
if (authorObj) {
|
|
152
|
+
author = await extractActorInfo(authorObj);
|
|
153
|
+
} else {
|
|
154
|
+
// Last resort: use attributionIds (non-fetching) to get at least a URL
|
|
155
|
+
const attrIds = object.attributionIds;
|
|
156
|
+
if (attrIds && attrIds.length > 0) {
|
|
157
|
+
const authorUrl = attrIds[0].href;
|
|
158
|
+
const authorHostname = new URL(authorUrl).hostname;
|
|
159
|
+
// Extract username from URL pattern like /users/name or /@name
|
|
160
|
+
const pathMatch = new URL(authorUrl).pathname.match(/\/@?([^/]+)/);
|
|
161
|
+
const username = pathMatch ? pathMatch[1] : "";
|
|
162
|
+
author = {
|
|
163
|
+
name: username || authorHostname,
|
|
164
|
+
url: authorUrl,
|
|
165
|
+
photo: "",
|
|
166
|
+
handle: username ? `@${username}@${authorHostname}` : "",
|
|
167
|
+
};
|
|
168
|
+
} else {
|
|
169
|
+
author = { name: "Unknown", url: "", photo: "", handle: "" };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
142
172
|
|
|
143
173
|
// Extract tags/categories
|
|
144
174
|
const category = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
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",
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{# Timeline item card partial - reusable across timeline and profile views #}
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
{# Skip empty cards (e.g. Lemmy/PieFed activity IDs with no actual content) #}
|
|
4
|
+
{% set hasCardContent = item.content and (item.content.html or item.content.text) %}
|
|
5
|
+
{% set hasCardTitle = item.name %}
|
|
6
|
+
{% set hasCardMedia = (item.photo and item.photo.length > 0) or (item.video and item.video.length > 0) or (item.audio and item.audio.length > 0) %}
|
|
7
|
+
{% if hasCardContent or hasCardTitle or hasCardMedia %}
|
|
8
|
+
|
|
9
|
+
<article class="ap-card{% if item.type %} ap-card--{{ item.type }}{% endif %}{% if item.inReplyTo %} ap-card--reply{% endif %}">
|
|
4
10
|
{# Boost header if this is a boosted post #}
|
|
5
11
|
{% if item.type == "boost" and item.boostedBy %}
|
|
6
12
|
<div class="ap-card__boost">
|
|
@@ -167,3 +173,5 @@
|
|
|
167
173
|
<div x-show="error" x-text="error" class="ap-card__action-error" x-transition></div>
|
|
168
174
|
</footer>
|
|
169
175
|
</article>
|
|
176
|
+
|
|
177
|
+
{% endif %}{# end hasCardContent/hasCardTitle/hasCardMedia guard #}
|