@rmdes/indiekit-endpoint-activitypub 2.7.0 → 2.8.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.
package/assets/reader.css CHANGED
@@ -528,12 +528,18 @@
528
528
  .ap-card__gallery img {
529
529
  background: var(--color-offset-variant);
530
530
  display: block;
531
- height: 220px;
531
+ height: 280px;
532
532
  object-fit: cover;
533
533
  width: 100%;
534
534
  transition: filter 0.2s ease;
535
535
  }
536
536
 
537
+ @media (max-width: 480px) {
538
+ .ap-card__gallery img {
539
+ height: 180px;
540
+ }
541
+ }
542
+
537
543
  .ap-card__gallery-link:hover img {
538
544
  filter: brightness(0.92);
539
545
  }
@@ -668,6 +674,83 @@
668
674
  transform: translateX(-50%);
669
675
  }
670
676
 
677
+ /* ==========================================================================
678
+ Link Preview Card
679
+ ========================================================================== */
680
+
681
+ .ap-link-previews {
682
+ margin-bottom: var(--space-s);
683
+ }
684
+
685
+ .ap-link-preview {
686
+ display: flex;
687
+ border: var(--border-width-thin) solid var(--color-outline);
688
+ border-radius: var(--border-radius-small);
689
+ overflow: hidden;
690
+ text-decoration: none;
691
+ color: inherit;
692
+ transition: border-color 0.2s ease;
693
+ }
694
+
695
+ .ap-link-preview:hover {
696
+ border-color: var(--color-primary);
697
+ }
698
+
699
+ .ap-link-preview__text {
700
+ flex: 1;
701
+ min-width: 0;
702
+ padding: var(--space-s) var(--space-m);
703
+ display: flex;
704
+ flex-direction: column;
705
+ justify-content: center;
706
+ gap: 0.2em;
707
+ }
708
+
709
+ .ap-link-preview__title {
710
+ font-weight: var(--font-weight-bold);
711
+ font-size: var(--font-size-s);
712
+ margin: 0;
713
+ overflow: hidden;
714
+ text-overflow: ellipsis;
715
+ white-space: nowrap;
716
+ }
717
+
718
+ .ap-link-preview__desc {
719
+ font-size: var(--font-size-s);
720
+ color: var(--color-on-offset);
721
+ margin: 0;
722
+ display: -webkit-box;
723
+ -webkit-line-clamp: 2;
724
+ -webkit-box-orient: vertical;
725
+ overflow: hidden;
726
+ }
727
+
728
+ .ap-link-preview__domain {
729
+ font-size: var(--font-size-xs);
730
+ color: var(--color-on-offset);
731
+ margin: 0;
732
+ display: flex;
733
+ align-items: center;
734
+ gap: 0.3em;
735
+ }
736
+
737
+ .ap-link-preview__favicon {
738
+ width: 14px;
739
+ height: 14px;
740
+ }
741
+
742
+ .ap-link-preview__image {
743
+ flex-shrink: 0;
744
+ width: 120px;
745
+ }
746
+
747
+ .ap-link-preview__image img {
748
+ display: block;
749
+ width: 100%;
750
+ height: 100%;
751
+ object-fit: cover;
752
+ }
753
+
671
754
  /* ==========================================================================
672
755
  Video Embed
673
756
  ========================================================================== */
@@ -431,6 +431,7 @@ export function registerInboxListeners(inboxChain, options) {
431
431
 
432
432
  await addNotification(collections, {
433
433
  uid: object.id?.href || `reply:${actorUrl}:${inReplyTo}`,
434
+ url: object.url?.href || object.id?.href || "",
434
435
  type: "reply",
435
436
  actorUrl: actorInfo.url,
436
437
  actorName: actorInfo.name,
@@ -462,6 +463,7 @@ export function registerInboxListeners(inboxChain, options) {
462
463
 
463
464
  await addNotification(collections, {
464
465
  uid: object.id?.href || `mention:${actorUrl}:${object.id?.href}`,
466
+ url: object.url?.href || object.id?.href || "",
465
467
  type: "mention",
466
468
  actorUrl: actorInfo.url,
467
469
  actorName: actorInfo.name,
package/lib/jf2-to-as2.js CHANGED
@@ -20,6 +20,22 @@ import {
20
20
  Video,
21
21
  } from "@fedify/fedify/vocab";
22
22
 
23
+ // ---------------------------------------------------------------------------
24
+ // Content helpers
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /**
28
+ * Convert bare URLs in HTML content to clickable links.
29
+ * Skips URLs already inside href attributes or anchor tag text.
30
+ */
31
+ function linkifyUrls(html) {
32
+ if (!html) return html;
33
+ return html.replace(
34
+ /(?<![=">])(https?:\/\/[^\s<"]+)/g,
35
+ '<a href="$1">$1</a>',
36
+ );
37
+ }
38
+
23
39
  // ---------------------------------------------------------------------------
24
40
  // Plain JSON-LD (content negotiation on individual post URLs)
25
41
  // ---------------------------------------------------------------------------
@@ -68,7 +84,7 @@ export function jf2ToActivityStreams(properties, actorUrl, publicationUrl) {
68
84
 
69
85
  if (postType === "bookmark") {
70
86
  const bookmarkUrl = properties["bookmark-of"];
71
- const commentary = properties.content?.html || properties.content || "";
87
+ const commentary = linkifyUrls(properties.content?.html || properties.content || "");
72
88
  object.content = commentary
73
89
  ? `${commentary}<br><br>\u{1F516} <a href="${bookmarkUrl}">${bookmarkUrl}</a>`
74
90
  : `\u{1F516} <a href="${bookmarkUrl}">${bookmarkUrl}</a>`;
@@ -80,7 +96,7 @@ export function jf2ToActivityStreams(properties, actorUrl, publicationUrl) {
80
96
  },
81
97
  ];
82
98
  } else {
83
- object.content = properties.content?.html || properties.content || "";
99
+ object.content = linkifyUrls(properties.content?.html || properties.content || "");
84
100
  }
85
101
 
86
102
  // Append permalink to content so fediverse clients show a clickable link
@@ -193,12 +209,12 @@ export function jf2ToAS2Activity(properties, actorUrl, publicationUrl, options =
193
209
  // Content
194
210
  if (postType === "bookmark") {
195
211
  const bookmarkUrl = properties["bookmark-of"];
196
- const commentary = properties.content?.html || properties.content || "";
212
+ const commentary = linkifyUrls(properties.content?.html || properties.content || "");
197
213
  noteOptions.content = commentary
198
214
  ? `${commentary}<br><br>\u{1F516} <a href="${bookmarkUrl}">${bookmarkUrl}</a>`
199
215
  : `\u{1F516} <a href="${bookmarkUrl}">${bookmarkUrl}</a>`;
200
216
  } else {
201
- noteOptions.content = properties.content?.html || properties.content || "";
217
+ noteOptions.content = linkifyUrls(properties.content?.html || properties.content || "");
202
218
  }
203
219
 
204
220
  // Append permalink to content so fediverse clients show a clickable link
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-activitypub",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
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",
@@ -230,7 +230,7 @@
230
230
  if (this.error) setTimeout(() => this.error = '', 3000);
231
231
  }
232
232
  }">
233
- <a href="{{ mountPath }}/admin/reader/compose?replyTo={{ itemUid | urlencode }}"
233
+ <a href="{{ mountPath }}/admin/reader/compose?replyTo={{ (itemUrl or itemUid) | urlencode }}"
234
234
  class="ap-card__action ap-card__action--reply"
235
235
  title="{{ __('activitypub.reader.actions.reply') }}">
236
236
  ↩ {{ __("activitypub.reader.actions.reply") }}{% if replyCount != null %}<span class="ap-card__count">{{ replyCount }}</span>{% endif %}
@@ -5,7 +5,7 @@
5
5
  {% set displayCount = item.photo.length if item.photo.length < 4 else 4 %}
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
+ <div x-data="{ lightbox: false, idx: 0, touchX: 0 }" class="ap-card__gallery ap-card__gallery--{{ displayCount }}">
9
9
  {% for photo in item.photo %}
10
10
  {# Support both old string format and new object format #}
11
11
  {% set photoSrc = photo.url if photo.url else photo %}
@@ -36,7 +36,7 @@
36
36
 
37
37
  {# Lightbox modal — teleported to body to prevent overflow clipping #}
38
38
  <template x-teleport="body">
39
- <div x-show="lightbox" x-cloak @keydown.escape.window="lightbox = false" @click.self="lightbox = false" class="ap-lightbox" role="dialog" aria-modal="true">
39
+ <div x-show="lightbox" x-cloak @keydown.escape.window="lightbox = false" @click.self="lightbox = false" @touchstart="touchX = $event.changedTouches[0].clientX" @touchend="let dx = $event.changedTouches[0].clientX - touchX; if (dx < -50) idx = (idx + 1) % {{ totalPhotos }}; else if (dx > 50) idx = (idx - 1 + {{ totalPhotos }}) % {{ totalPhotos }}" class="ap-lightbox" role="dialog" aria-modal="true">
40
40
  <button type="button" @click="lightbox = false" class="ap-lightbox__close" aria-label="Close">&times;</button>
41
41
  {% if totalPhotos > 1 %}
42
42
  <button type="button" @click="idx = (idx - 1 + {{ totalPhotos }}) % {{ totalPhotos }}" class="ap-lightbox__prev" aria-label="Previous image">&lsaquo;</button>
@@ -56,10 +56,10 @@
56
56
 
57
57
  {% if item.type == "reply" or item.type == "mention" %}
58
58
  <div class="ap-notification__actions">
59
- <a href="{{ mountPath }}/admin/reader/compose?replyTo={{ item.uid | urlencode }}" class="ap-notification__reply-btn" title="{{ __('activitypub.reader.actions.reply') }}">
59
+ <a href="{{ mountPath }}/admin/reader/compose?replyTo={{ (item.url or item.uid) | urlencode }}" class="ap-notification__reply-btn" title="{{ __('activitypub.reader.actions.reply') }}">
60
60
  ↩ {{ __("activitypub.reader.actions.reply") }}
61
61
  </a>
62
- <a href="{{ mountPath }}/admin/reader/post?url={{ item.uid | urlencode }}" class="ap-notification__thread-btn" title="{{ __('activitypub.reader.post.title') }}">
62
+ <a href="{{ mountPath }}/admin/reader/post?url={{ (item.url or item.uid) | urlencode }}" class="ap-notification__thread-btn" title="{{ __('activitypub.reader.post.title') }}">
63
63
  💬 {{ __("activitypub.notifications.viewThread") }}
64
64
  </a>
65
65
  </div>