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 %} -