@rmdes/indiekit-endpoint-activitypub 2.0.16 → 2.0.18
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 +82 -0
- package/index.js +100 -92
- package/lib/timeline-store.js +2 -2
- package/package.json +1 -1
- package/views/partials/ap-item-media.njk +20 -4
package/assets/reader.css
CHANGED
|
@@ -457,7 +457,12 @@
|
|
|
457
457
|
}
|
|
458
458
|
|
|
459
459
|
.ap-card__gallery-link {
|
|
460
|
+
appearance: none;
|
|
461
|
+
background: none;
|
|
462
|
+
border: 0;
|
|
463
|
+
cursor: pointer;
|
|
460
464
|
display: block;
|
|
465
|
+
padding: 0;
|
|
461
466
|
position: relative;
|
|
462
467
|
}
|
|
463
468
|
|
|
@@ -522,6 +527,83 @@
|
|
|
522
527
|
grid-template-rows: 1fr 1fr;
|
|
523
528
|
}
|
|
524
529
|
|
|
530
|
+
/* ==========================================================================
|
|
531
|
+
Photo Lightbox
|
|
532
|
+
========================================================================== */
|
|
533
|
+
|
|
534
|
+
[x-cloak] {
|
|
535
|
+
display: none !important;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.ap-lightbox {
|
|
539
|
+
align-items: center;
|
|
540
|
+
background: rgba(0, 0, 0, 0.92);
|
|
541
|
+
display: flex;
|
|
542
|
+
inset: 0;
|
|
543
|
+
justify-content: center;
|
|
544
|
+
position: fixed;
|
|
545
|
+
z-index: 9999;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.ap-lightbox__img {
|
|
549
|
+
max-height: 90vh;
|
|
550
|
+
max-width: 95vw;
|
|
551
|
+
object-fit: contain;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.ap-lightbox__close {
|
|
555
|
+
background: none;
|
|
556
|
+
border: 0;
|
|
557
|
+
color: white;
|
|
558
|
+
cursor: pointer;
|
|
559
|
+
font-size: 2rem;
|
|
560
|
+
line-height: 1;
|
|
561
|
+
padding: var(--space-s);
|
|
562
|
+
position: absolute;
|
|
563
|
+
right: var(--space-m);
|
|
564
|
+
top: var(--space-m);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.ap-lightbox__close:hover {
|
|
568
|
+
opacity: 0.7;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.ap-lightbox__prev,
|
|
572
|
+
.ap-lightbox__next {
|
|
573
|
+
background: none;
|
|
574
|
+
border: 0;
|
|
575
|
+
color: white;
|
|
576
|
+
cursor: pointer;
|
|
577
|
+
font-size: 3rem;
|
|
578
|
+
line-height: 1;
|
|
579
|
+
padding: var(--space-m);
|
|
580
|
+
position: absolute;
|
|
581
|
+
top: 50%;
|
|
582
|
+
transform: translateY(-50%);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.ap-lightbox__prev {
|
|
586
|
+
left: var(--space-s);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.ap-lightbox__next {
|
|
590
|
+
right: var(--space-s);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.ap-lightbox__prev:hover,
|
|
594
|
+
.ap-lightbox__next:hover {
|
|
595
|
+
opacity: 0.7;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.ap-lightbox__counter {
|
|
599
|
+
bottom: var(--space-m);
|
|
600
|
+
color: white;
|
|
601
|
+
font-size: var(--font-size-s);
|
|
602
|
+
left: 50%;
|
|
603
|
+
position: absolute;
|
|
604
|
+
transform: translateX(-50%);
|
|
605
|
+
}
|
|
606
|
+
|
|
525
607
|
/* ==========================================================================
|
|
526
608
|
Video Embed
|
|
527
609
|
========================================================================== */
|
package/index.js
CHANGED
|
@@ -875,107 +875,115 @@ export default class ActivityPubEndpoint {
|
|
|
875
875
|
_publicationUrl: this._publicationUrl,
|
|
876
876
|
};
|
|
877
877
|
|
|
878
|
-
//
|
|
879
|
-
|
|
880
|
-
|
|
878
|
+
// Create indexes — wrapped in try-catch because collection references
|
|
879
|
+
// may be undefined if MongoDB hasn't finished connecting yet.
|
|
880
|
+
// Indexes are idempotent; they'll be created on next successful startup.
|
|
881
|
+
try {
|
|
882
|
+
// TTL index for activity cleanup (MongoDB handles expiry automatically)
|
|
883
|
+
const retentionDays = this.options.activityRetentionDays;
|
|
884
|
+
if (retentionDays > 0) {
|
|
885
|
+
this._collections.ap_activities.createIndex(
|
|
886
|
+
{ receivedAt: 1 },
|
|
887
|
+
{ expireAfterSeconds: retentionDays * 86_400 },
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Performance indexes for inbox handlers and batch refollow
|
|
892
|
+
this._collections.ap_followers.createIndex(
|
|
893
|
+
{ actorUrl: 1 },
|
|
894
|
+
{ unique: true, background: true },
|
|
895
|
+
);
|
|
896
|
+
this._collections.ap_following.createIndex(
|
|
897
|
+
{ actorUrl: 1 },
|
|
898
|
+
{ unique: true, background: true },
|
|
899
|
+
);
|
|
900
|
+
this._collections.ap_following.createIndex(
|
|
901
|
+
{ source: 1 },
|
|
902
|
+
{ background: true },
|
|
903
|
+
);
|
|
881
904
|
this._collections.ap_activities.createIndex(
|
|
882
|
-
{
|
|
883
|
-
{
|
|
905
|
+
{ objectUrl: 1 },
|
|
906
|
+
{ background: true },
|
|
907
|
+
);
|
|
908
|
+
this._collections.ap_activities.createIndex(
|
|
909
|
+
{ type: 1, actorUrl: 1, objectUrl: 1 },
|
|
910
|
+
{ background: true },
|
|
884
911
|
);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// Performance indexes for inbox handlers and batch refollow
|
|
888
|
-
this._collections.ap_followers.createIndex(
|
|
889
|
-
{ actorUrl: 1 },
|
|
890
|
-
{ unique: true, background: true },
|
|
891
|
-
);
|
|
892
|
-
this._collections.ap_following.createIndex(
|
|
893
|
-
{ actorUrl: 1 },
|
|
894
|
-
{ unique: true, background: true },
|
|
895
|
-
);
|
|
896
|
-
this._collections.ap_following.createIndex(
|
|
897
|
-
{ source: 1 },
|
|
898
|
-
{ background: true },
|
|
899
|
-
);
|
|
900
|
-
this._collections.ap_activities.createIndex(
|
|
901
|
-
{ objectUrl: 1 },
|
|
902
|
-
{ background: true },
|
|
903
|
-
);
|
|
904
|
-
this._collections.ap_activities.createIndex(
|
|
905
|
-
{ type: 1, actorUrl: 1, objectUrl: 1 },
|
|
906
|
-
{ background: true },
|
|
907
|
-
);
|
|
908
|
-
|
|
909
|
-
// Reader indexes (timeline, notifications, moderation, interactions)
|
|
910
|
-
this._collections.ap_timeline.createIndex(
|
|
911
|
-
{ uid: 1 },
|
|
912
|
-
{ unique: true, background: true },
|
|
913
|
-
);
|
|
914
|
-
this._collections.ap_timeline.createIndex(
|
|
915
|
-
{ published: -1 },
|
|
916
|
-
{ background: true },
|
|
917
|
-
);
|
|
918
|
-
this._collections.ap_timeline.createIndex(
|
|
919
|
-
{ "author.url": 1 },
|
|
920
|
-
{ background: true },
|
|
921
|
-
);
|
|
922
|
-
this._collections.ap_timeline.createIndex(
|
|
923
|
-
{ type: 1, published: -1 },
|
|
924
|
-
{ background: true },
|
|
925
|
-
);
|
|
926
912
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
913
|
+
// Reader indexes (timeline, notifications, moderation, interactions)
|
|
914
|
+
this._collections.ap_timeline.createIndex(
|
|
915
|
+
{ uid: 1 },
|
|
916
|
+
{ unique: true, background: true },
|
|
917
|
+
);
|
|
918
|
+
this._collections.ap_timeline.createIndex(
|
|
919
|
+
{ published: -1 },
|
|
920
|
+
{ background: true },
|
|
921
|
+
);
|
|
922
|
+
this._collections.ap_timeline.createIndex(
|
|
923
|
+
{ "author.url": 1 },
|
|
924
|
+
{ background: true },
|
|
925
|
+
);
|
|
926
|
+
this._collections.ap_timeline.createIndex(
|
|
927
|
+
{ type: 1, published: -1 },
|
|
928
|
+
{ background: true },
|
|
929
|
+
);
|
|
943
930
|
|
|
944
|
-
// TTL index for notification cleanup
|
|
945
|
-
const notifRetention = this.options.notificationRetentionDays;
|
|
946
|
-
if (notifRetention > 0) {
|
|
947
931
|
this._collections.ap_notifications.createIndex(
|
|
948
|
-
{
|
|
949
|
-
{
|
|
932
|
+
{ uid: 1 },
|
|
933
|
+
{ unique: true, background: true },
|
|
934
|
+
);
|
|
935
|
+
this._collections.ap_notifications.createIndex(
|
|
936
|
+
{ published: -1 },
|
|
937
|
+
{ background: true },
|
|
938
|
+
);
|
|
939
|
+
this._collections.ap_notifications.createIndex(
|
|
940
|
+
{ read: 1 },
|
|
941
|
+
{ background: true },
|
|
942
|
+
);
|
|
943
|
+
this._collections.ap_notifications.createIndex(
|
|
944
|
+
{ type: 1, published: -1 },
|
|
945
|
+
{ background: true },
|
|
950
946
|
);
|
|
951
|
-
}
|
|
952
947
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
this._collections.ap_muted.createIndex(
|
|
962
|
-
{ keyword: 1 },
|
|
963
|
-
{ unique: true, sparse: true, background: true },
|
|
964
|
-
);
|
|
948
|
+
// TTL index for notification cleanup
|
|
949
|
+
const notifRetention = this.options.notificationRetentionDays;
|
|
950
|
+
if (notifRetention > 0) {
|
|
951
|
+
this._collections.ap_notifications.createIndex(
|
|
952
|
+
{ createdAt: 1 },
|
|
953
|
+
{ expireAfterSeconds: notifRetention * 86_400 },
|
|
954
|
+
);
|
|
955
|
+
}
|
|
965
956
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
957
|
+
// Drop non-sparse indexes if they exist (created by earlier versions),
|
|
958
|
+
// then recreate with sparse:true so multiple null values are allowed.
|
|
959
|
+
this._collections.ap_muted.dropIndex("url_1").catch(() => {});
|
|
960
|
+
this._collections.ap_muted.dropIndex("keyword_1").catch(() => {});
|
|
961
|
+
this._collections.ap_muted.createIndex(
|
|
962
|
+
{ url: 1 },
|
|
963
|
+
{ unique: true, sparse: true, background: true },
|
|
964
|
+
);
|
|
965
|
+
this._collections.ap_muted.createIndex(
|
|
966
|
+
{ keyword: 1 },
|
|
967
|
+
{ unique: true, sparse: true, background: true },
|
|
968
|
+
);
|
|
970
969
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
970
|
+
this._collections.ap_blocked.createIndex(
|
|
971
|
+
{ url: 1 },
|
|
972
|
+
{ unique: true, background: true },
|
|
973
|
+
);
|
|
974
|
+
|
|
975
|
+
this._collections.ap_interactions.createIndex(
|
|
976
|
+
{ objectUrl: 1, type: 1 },
|
|
977
|
+
{ unique: true, background: true },
|
|
978
|
+
);
|
|
979
|
+
this._collections.ap_interactions.createIndex(
|
|
980
|
+
{ type: 1 },
|
|
981
|
+
{ background: true },
|
|
982
|
+
);
|
|
983
|
+
} catch {
|
|
984
|
+
// Index creation failed — collections not yet available.
|
|
985
|
+
// Indexes already exist from previous startups; non-fatal.
|
|
986
|
+
}
|
|
979
987
|
|
|
980
988
|
// Seed actor profile from config on first run
|
|
981
989
|
this._seedProfile().catch((error) => {
|
package/lib/timeline-store.js
CHANGED
|
@@ -185,7 +185,7 @@ export async function extractObjectData(object, options = {}) {
|
|
|
185
185
|
try {
|
|
186
186
|
if (typeof object.getTags === "function") {
|
|
187
187
|
const tags = await object.getTags();
|
|
188
|
-
for (const tag of tags) {
|
|
188
|
+
for await (const tag of tags) {
|
|
189
189
|
if (tag.name) {
|
|
190
190
|
const tagName = tag.name.toString().replace(/^#/, "");
|
|
191
191
|
if (tagName) category.push(tagName);
|
|
@@ -204,7 +204,7 @@ export async function extractObjectData(object, options = {}) {
|
|
|
204
204
|
try {
|
|
205
205
|
if (typeof object.getAttachments === "function") {
|
|
206
206
|
const attachments = await object.getAttachments();
|
|
207
|
-
for (const att of attachments) {
|
|
207
|
+
for await (const att of attachments) {
|
|
208
208
|
const mediaUrl = att.url?.href || "";
|
|
209
209
|
if (!mediaUrl) continue;
|
|
210
210
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.18",
|
|
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,20 +1,36 @@
|
|
|
1
1
|
{# Media attachments partial — included from ap-item-card.njk #}
|
|
2
2
|
|
|
3
|
-
{# Photo gallery #}
|
|
3
|
+
{# Photo gallery with lightbox #}
|
|
4
4
|
{% if item.photo and item.photo.length > 0 %}
|
|
5
5
|
{% set displayCount = [item.photo.length, 4] | min %}
|
|
6
6
|
{% set extraCount = item.photo.length - 4 %}
|
|
7
|
-
|
|
7
|
+
{% set totalPhotos = item.photo.length %}
|
|
8
|
+
<div x-data="{ lightbox: false, idx: 0 }" class="ap-card__gallery ap-card__gallery--{{ displayCount }}">
|
|
8
9
|
{% for photoUrl in item.photo %}
|
|
9
10
|
{% if loop.index0 < 4 %}
|
|
10
|
-
<
|
|
11
|
+
<button type="button" @click="idx = {{ loop.index0 }}; lightbox = true" class="ap-card__gallery-link{% if loop.index0 == 3 and extraCount > 0 %} ap-card__gallery-link--more{% endif %}">
|
|
11
12
|
<img src="{{ photoUrl }}" alt="" loading="lazy">
|
|
12
13
|
{% if loop.index0 == 3 and extraCount > 0 %}
|
|
13
14
|
<span class="ap-card__gallery-more">+{{ extraCount }}</span>
|
|
14
15
|
{% endif %}
|
|
15
|
-
</
|
|
16
|
+
</button>
|
|
16
17
|
{% endif %}
|
|
17
18
|
{% endfor %}
|
|
19
|
+
|
|
20
|
+
{# Lightbox modal — teleported to body to prevent overflow clipping #}
|
|
21
|
+
<template x-teleport="body">
|
|
22
|
+
<div x-show="lightbox" x-cloak @keydown.escape.window="lightbox = false" @click.self="lightbox = false" class="ap-lightbox" role="dialog" aria-modal="true">
|
|
23
|
+
<button type="button" @click="lightbox = false" class="ap-lightbox__close" aria-label="Close">×</button>
|
|
24
|
+
{% if totalPhotos > 1 %}
|
|
25
|
+
<button type="button" @click="idx = (idx - 1 + {{ totalPhotos }}) % {{ totalPhotos }}" class="ap-lightbox__prev" aria-label="Previous image">‹</button>
|
|
26
|
+
{% endif %}
|
|
27
|
+
<img :src="[{% for p in item.photo %}'{{ p }}'{% if not loop.last %},{% endif %}{% endfor %}][idx]" class="ap-lightbox__img" alt="">
|
|
28
|
+
{% if totalPhotos > 1 %}
|
|
29
|
+
<button type="button" @click="idx = (idx + 1) % {{ totalPhotos }}" class="ap-lightbox__next" aria-label="Next image">›</button>
|
|
30
|
+
<div class="ap-lightbox__counter" x-text="(idx + 1) + ' / ' + {{ totalPhotos }}"></div>
|
|
31
|
+
{% endif %}
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
18
34
|
</div>
|
|
19
35
|
{% endif %}
|
|
20
36
|
|