feat: add unfurl cards to collection views and homepage

Show rich link preview cards in bookmarks, likes, replies, reposts
collection pages and the homepage recent posts section. URLs are
fetched once and cached — the same cache serves all templates.
This commit is contained in:
Ricardo
2026-02-20 12:39:11 +01:00
parent 41c7fae2f1
commit 0f496d624f
5 changed files with 30 additions and 40 deletions
+8 -12
View File
@@ -40,11 +40,10 @@
{{ post.date | dateDisplay }} {{ post.date | dateDisplay }}
</time> </time>
</div> </div>
<p class="mt-1"> {% unfurl likedUrl %}
<a class="u-like-of text-sm text-primary-600 dark:text-primary-400 hover:underline break-all" href="{{ likedUrl }}"> <a class="u-like-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ likedUrl }}">
{{ likedUrl }} {{ likedUrl }}
</a> </a>
</p>
{% if post.templateContent %} {% if post.templateContent %}
<div class="e-content prose dark:prose-invert prose-sm mt-2 max-w-none line-clamp-3"> <div class="e-content prose dark:prose-invert prose-sm mt-2 max-w-none line-clamp-3">
{{ post.templateContent | safe }} {{ post.templateContent | safe }}
@@ -74,11 +73,10 @@
<a class="hover:text-primary-600 dark:hover:text-primary-400" href="{{ post.url }}">{{ post.data.title }}</a> <a class="hover:text-primary-600 dark:hover:text-primary-400" href="{{ post.url }}">{{ post.data.title }}</a>
</h3> </h3>
{% endif %} {% endif %}
<p class="mt-1 text-sm"> {% unfurl bookmarkedUrl %}
<a class="u-bookmark-of text-primary-600 dark:text-primary-400 hover:underline break-all" href="{{ bookmarkedUrl }}"> <a class="u-bookmark-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ bookmarkedUrl }}">
{{ bookmarkedUrl }} {{ bookmarkedUrl }}
</a> </a>
</p>
{% if post.templateContent %} {% if post.templateContent %}
<div class="e-content prose dark:prose-invert prose-sm mt-2 max-w-none line-clamp-3"> <div class="e-content prose dark:prose-invert prose-sm mt-2 max-w-none line-clamp-3">
{{ post.templateContent | safe }} {{ post.templateContent | safe }}
@@ -103,11 +101,10 @@
{{ post.date | dateDisplay }} {{ post.date | dateDisplay }}
</time> </time>
</div> </div>
<p class="mt-1 text-sm"> {% unfurl repostedUrl %}
<a class="u-repost-of text-primary-600 dark:text-primary-400 hover:underline break-all" href="{{ repostedUrl }}"> <a class="u-repost-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ repostedUrl }}">
{{ repostedUrl }} {{ repostedUrl }}
</a> </a>
</p>
{% if post.templateContent %} {% if post.templateContent %}
<div class="e-content prose dark:prose-invert prose-sm mt-2 max-w-none line-clamp-3"> <div class="e-content prose dark:prose-invert prose-sm mt-2 max-w-none line-clamp-3">
{{ post.templateContent | safe }} {{ post.templateContent | safe }}
@@ -132,11 +129,10 @@
{{ post.date | dateDisplay }} {{ post.date | dateDisplay }}
</time> </time>
</div> </div>
<p class="mt-1 text-sm"> {% unfurl replyToUrl %}
<a class="u-in-reply-to text-primary-600 dark:text-primary-400 hover:underline break-all" href="{{ replyToUrl }}"> <a class="u-in-reply-to text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ replyToUrl }}">
{{ replyToUrl }} {{ replyToUrl }}
</a> </a>
</p>
{% if post.templateContent %} {% if post.templateContent %}
<div class="e-content prose dark:prose-invert prose-sm mt-2 max-w-none line-clamp-3"> <div class="e-content prose dark:prose-invert prose-sm mt-2 max-w-none line-clamp-3">
{{ post.templateContent | safe }} {{ post.templateContent | safe }}
+2 -6
View File
@@ -54,14 +54,10 @@ permalink: "bookmarks/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageN
{# Support both camelCase (Indiekit Eleventy preset) and underscore (legacy) property names #} {# Support both camelCase (Indiekit Eleventy preset) and underscore (legacy) property names #}
{% set bookmarkedUrl = post.data.bookmarkOf or post.data.bookmark_of %} {% set bookmarkedUrl = post.data.bookmarkOf or post.data.bookmark_of %}
{% if bookmarkedUrl %} {% if bookmarkedUrl %}
<p class="mt-3 flex items-center gap-2 text-sm"> {% unfurl bookmarkedUrl %}
<svg class="w-4 h-4 text-primary-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <a class="u-bookmark-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ bookmarkedUrl }}">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"/>
</svg>
<a class="u-bookmark-of text-primary-600 dark:text-primary-400 hover:underline break-all" href="{{ bookmarkedUrl }}">
{{ bookmarkedUrl }} {{ bookmarkedUrl }}
</a> </a>
</p>
{% endif %} {% endif %}
{% if post.templateContent %} {% if post.templateContent %}
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none"> <div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
+2 -3
View File
@@ -52,11 +52,10 @@ permalink: "likes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
{# Support both camelCase (Indiekit Eleventy preset) and underscore (legacy) property names #} {# Support both camelCase (Indiekit Eleventy preset) and underscore (legacy) property names #}
{% set likedUrl = post.data.likeOf or post.data.like_of %} {% set likedUrl = post.data.likeOf or post.data.like_of %}
{% if likedUrl %} {% if likedUrl %}
<p class="mt-2"> {% unfurl likedUrl %}
<a class="u-like-of text-primary-600 dark:text-primary-400 hover:underline break-all" href="{{ likedUrl }}"> <a class="u-like-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ likedUrl }}">
{{ likedUrl }} {{ likedUrl }}
</a> </a>
</p>
{% endif %} {% endif %}
{% if post.templateContent %} {% if post.templateContent %}
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none"> <div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
+2 -1
View File
@@ -58,9 +58,10 @@ permalink: "replies/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
{% set replyTo = post.data.inReplyTo or post.data.in_reply_to %} {% set replyTo = post.data.inReplyTo or post.data.in_reply_to %}
{% if replyTo %} {% if replyTo %}
{% set protocol = replyTo | protocolType %} {% set protocol = replyTo | protocolType %}
{% unfurl replyTo %}
<p class="mt-2 text-sm flex items-center gap-2 flex-wrap"> <p class="mt-2 text-sm flex items-center gap-2 flex-wrap">
<span class="text-surface-500">In reply to:</span> <span class="text-surface-500">In reply to:</span>
<a class="u-in-reply-to text-primary-600 dark:text-primary-400 hover:underline break-all" href="{{ replyTo }}"> <a class="u-in-reply-to text-xs text-surface-400 dark:text-surface-500 hover:underline break-all" href="{{ replyTo }}">
{{ replyTo | replace("https://", "") | replace("http://", "") | truncate(60) }} {{ replyTo | replace("https://", "") | replace("http://", "") | truncate(60) }}
</a> </a>
{% if protocol == "atmosphere" %} {% if protocol == "atmosphere" %}
+2 -4
View File
@@ -57,12 +57,10 @@ permalink: "reposts/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
{# Support both camelCase (Indiekit Eleventy preset) and underscore (legacy) property names #} {# Support both camelCase (Indiekit Eleventy preset) and underscore (legacy) property names #}
{% set repostedUrl = post.data.repostOf or post.data.repost_of %} {% set repostedUrl = post.data.repostOf or post.data.repost_of %}
{% if repostedUrl %} {% if repostedUrl %}
<p class="mt-2 text-sm"> {% unfurl repostedUrl %}
<span class="text-surface-500">Reposted:</span> <a class="u-repost-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ repostedUrl }}">
<a class="u-repost-of text-primary-600 dark:text-primary-400 hover:underline break-all" href="{{ repostedUrl }}">
{{ repostedUrl }} {{ repostedUrl }}
</a> </a>
</p>
{% endif %} {% endif %}
{% if post.templateContent %} {% if post.templateContent %}
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none"> <div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">