feat: See Also and Linked From sections on posts
- `backlinksWith` filter scans raw source files to find posts that link to the current post (backlinks), with per-build caching. - `postByUrl` filter looks up a post by absolute URL for title display. - post.njk: "See Also" renders resolved `related` URLs with titles; "Linked From" lists backlinks computed at build time. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -338,6 +338,40 @@ withBlogSidebar: true
|
||||
</article>
|
||||
|
||||
|
||||
{# See Also — related posts from frontmatter (resolved wikilinks → URLs) #}
|
||||
{% if related and related.length > 0 %}
|
||||
<section class="post-related mt-8 pt-6 border-t border-surface-200 dark:border-surface-700">
|
||||
<h2 class="text-xs font-semibold text-surface-500 dark:text-surface-400 uppercase tracking-widest mb-3">See Also</h2>
|
||||
<ul class="space-y-1.5 list-none p-0 m-0">
|
||||
{% for relUrl in related %}
|
||||
{% set _relPost = collections.posts | postByUrl(relUrl) %}
|
||||
<li>
|
||||
<a href="{{ relUrl }}" class="text-sm text-accent-700 dark:text-accent-300 hover:underline">
|
||||
{{ _relPost.data.title if _relPost else relUrl }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{# Linked From — posts that link to this one, computed from raw source files #}
|
||||
{% set _backlinks = collections.posts | backlinksWith(page.url) %}
|
||||
{% if _backlinks and _backlinks.length > 0 %}
|
||||
<section class="post-backlinks mt-6 pt-6 border-t border-surface-200 dark:border-surface-700">
|
||||
<h2 class="text-xs font-semibold text-surface-500 dark:text-surface-400 uppercase tracking-widest mb-3">Linked From</h2>
|
||||
<ul class="space-y-1.5 list-none p-0 m-0">
|
||||
{% for post in _backlinks %}
|
||||
<li>
|
||||
<a href="{{ post.url }}" class="text-sm text-accent-700 dark:text-accent-300 hover:underline">
|
||||
{{ post.data.title or "Untitled" }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{# Comments section #}
|
||||
{% include "components/comments.njk" %}
|
||||
|
||||
|
||||
@@ -1094,6 +1094,31 @@ export default function (eleventyConfig) {
|
||||
return stages[stage] || null;
|
||||
});
|
||||
|
||||
// Backlinks — find all published posts whose raw source links to the given URL path.
|
||||
// Reads each file once per build and caches it to avoid repeated disk I/O.
|
||||
{
|
||||
const contentCache = new Map();
|
||||
eleventyConfig.addFilter("backlinksWith", function (posts, currentUrl) {
|
||||
const fullUrl = `${siteUrl}${currentUrl}`;
|
||||
return (posts || []).filter((post) => {
|
||||
if (post.url === currentUrl) return false;
|
||||
let content = contentCache.get(post.inputPath);
|
||||
if (content === undefined) {
|
||||
try { content = readFileSync(post.inputPath, "utf-8"); }
|
||||
catch { content = ""; }
|
||||
contentCache.set(post.inputPath, content);
|
||||
}
|
||||
return content.includes(fullUrl);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Look up a post by its absolute blog URL — used to show titles in related-links lists.
|
||||
eleventyConfig.addFilter("postByUrl", function (posts, url) {
|
||||
const path = url.replace(siteUrl, "");
|
||||
return (posts || []).find((p) => p.url === path || p.url === `${path}/`);
|
||||
});
|
||||
|
||||
// Strip garden/* tags from a category list so they don't render as
|
||||
// plain category pills alongside the garden badge.
|
||||
eleventyConfig.addFilter("withoutGardenTags", (categories) => {
|
||||
|
||||
Reference in New Issue
Block a user