diff --git a/assets/reader.css b/assets/reader.css
index 7d0b8c5..6157e20 100644
--- a/assets/reader.css
+++ b/assets/reader.css
@@ -528,12 +528,18 @@
.ap-card__gallery img {
background: var(--color-offset-variant);
display: block;
- height: 220px;
+ height: 280px;
object-fit: cover;
width: 100%;
transition: filter 0.2s ease;
}
+@media (max-width: 480px) {
+ .ap-card__gallery img {
+ height: 180px;
+ }
+}
+
.ap-card__gallery-link:hover img {
filter: brightness(0.92);
}
@@ -668,6 +674,83 @@
transform: translateX(-50%);
}
+/* ==========================================================================
+ Link Preview Card
+ ========================================================================== */
+
+.ap-link-previews {
+ margin-bottom: var(--space-s);
+}
+
+.ap-link-preview {
+ display: flex;
+ border: var(--border-width-thin) solid var(--color-outline);
+ border-radius: var(--border-radius-small);
+ overflow: hidden;
+ text-decoration: none;
+ color: inherit;
+ transition: border-color 0.2s ease;
+}
+
+.ap-link-preview:hover {
+ border-color: var(--color-primary);
+}
+
+.ap-link-preview__text {
+ flex: 1;
+ min-width: 0;
+ padding: var(--space-s) var(--space-m);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ gap: 0.2em;
+}
+
+.ap-link-preview__title {
+ font-weight: var(--font-weight-bold);
+ font-size: var(--font-size-s);
+ margin: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.ap-link-preview__desc {
+ font-size: var(--font-size-s);
+ color: var(--color-on-offset);
+ margin: 0;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.ap-link-preview__domain {
+ font-size: var(--font-size-xs);
+ color: var(--color-on-offset);
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 0.3em;
+}
+
+.ap-link-preview__favicon {
+ width: 14px;
+ height: 14px;
+}
+
+.ap-link-preview__image {
+ flex-shrink: 0;
+ width: 120px;
+}
+
+.ap-link-preview__image img {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
/* ==========================================================================
Video Embed
========================================================================== */
diff --git a/lib/jf2-to-as2.js b/lib/jf2-to-as2.js
index 7597436..6bfb119 100644
--- a/lib/jf2-to-as2.js
+++ b/lib/jf2-to-as2.js
@@ -20,6 +20,22 @@ import {
Video,
} from "@fedify/fedify/vocab";
+// ---------------------------------------------------------------------------
+// Content helpers
+// ---------------------------------------------------------------------------
+
+/**
+ * Convert bare URLs in HTML content to clickable links.
+ * Skips URLs already inside href attributes or anchor tag text.
+ */
+function linkifyUrls(html) {
+ if (!html) return html;
+ return html.replace(
+ /(?])(https?:\/\/[^\s<"]+)/g,
+ '$1',
+ );
+}
+
// ---------------------------------------------------------------------------
// Plain JSON-LD (content negotiation on individual post URLs)
// ---------------------------------------------------------------------------
@@ -68,7 +84,7 @@ export function jf2ToActivityStreams(properties, actorUrl, publicationUrl) {
if (postType === "bookmark") {
const bookmarkUrl = properties["bookmark-of"];
- const commentary = properties.content?.html || properties.content || "";
+ const commentary = linkifyUrls(properties.content?.html || properties.content || "");
object.content = commentary
? `${commentary}
\u{1F516} ${bookmarkUrl}`
: `\u{1F516} ${bookmarkUrl}`;
@@ -80,7 +96,7 @@ export function jf2ToActivityStreams(properties, actorUrl, publicationUrl) {
},
];
} else {
- object.content = properties.content?.html || properties.content || "";
+ object.content = linkifyUrls(properties.content?.html || properties.content || "");
}
// Append permalink to content so fediverse clients show a clickable link
@@ -193,12 +209,12 @@ export function jf2ToAS2Activity(properties, actorUrl, publicationUrl, options =
// Content
if (postType === "bookmark") {
const bookmarkUrl = properties["bookmark-of"];
- const commentary = properties.content?.html || properties.content || "";
+ const commentary = linkifyUrls(properties.content?.html || properties.content || "");
noteOptions.content = commentary
? `${commentary}
\u{1F516} ${bookmarkUrl}`
: `\u{1F516} ${bookmarkUrl}`;
} else {
- noteOptions.content = properties.content?.html || properties.content || "";
+ noteOptions.content = linkifyUrls(properties.content?.html || properties.content || "");
}
// Append permalink to content so fediverse clients show a clickable link
diff --git a/package.json b/package.json
index aaec421..b6238ee 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@rmdes/indiekit-endpoint-activitypub",
- "version": "2.7.1",
+ "version": "2.8.0",
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
"keywords": [
"indiekit",
diff --git a/views/partials/ap-item-media.njk b/views/partials/ap-item-media.njk
index e3a5df1..c15cdd7 100644
--- a/views/partials/ap-item-media.njk
+++ b/views/partials/ap-item-media.njk
@@ -5,7 +5,7 @@
{% set displayCount = item.photo.length if item.photo.length < 4 else 4 %}
{% set extraCount = item.photo.length - 4 %}
{% set totalPhotos = item.photo.length %}
-