feat: See Also includes in-text links to other blog posts
Adds seeAlsoLinks filter that extracts all internal blog.giersig.eu links from the raw markdown source and merges them with the explicit `related` frontmatter field, deduplicated. The section now surfaces automatically whenever an article links to another post inline. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -128,12 +128,13 @@ withBlogSidebar: true
|
|||||||
</details>
|
</details>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# See Also — related posts from frontmatter (resolved wikilinks → URLs) #}
|
{# See Also — explicit related: frontmatter + in-text links to other blog posts #}
|
||||||
{% if related and related.length > 0 %}
|
{% set _seeAlso = page.inputPath | seeAlsoLinks(related) %}
|
||||||
|
{% if _seeAlso.length > 0 %}
|
||||||
<section class="post-related mt-6 pt-5 border-t border-surface-200 dark:border-surface-700">
|
<section class="post-related mt-6 pt-5 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>
|
<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">
|
<ul class="space-y-1.5 list-none p-0 m-0">
|
||||||
{% for relUrl in related %}
|
{% for relUrl in _seeAlso %}
|
||||||
{% set _relPost = collections.posts | postByUrl(relUrl) %}
|
{% set _relPost = collections.posts | postByUrl(relUrl) %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ relUrl }}" class="text-sm text-accent-700 dark:text-accent-300 hover:underline">
|
<a href="{{ relUrl }}" class="text-sm text-accent-700 dark:text-accent-300 hover:underline">
|
||||||
|
|||||||
@@ -1119,6 +1119,26 @@ export default function (eleventyConfig) {
|
|||||||
return (posts || []).find((p) => p.url === path || p.url === `${path}/`);
|
return (posts || []).find((p) => p.url === path || p.url === `${path}/`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Collect all internal blog links from a post's raw markdown and merge with
|
||||||
|
// the explicit `related` frontmatter URLs. Returns a deduplicated list.
|
||||||
|
eleventyConfig.addFilter("seeAlsoLinks", function (inputPath, relatedUrls) {
|
||||||
|
const seen = new Set();
|
||||||
|
const result = [];
|
||||||
|
const add = (url) => {
|
||||||
|
const key = String(url).replace(/\/$/, "");
|
||||||
|
if (!seen.has(key)) { seen.add(key); result.push(String(url)); }
|
||||||
|
};
|
||||||
|
for (const url of (relatedUrls || [])) add(url);
|
||||||
|
try {
|
||||||
|
const content = readFileSync(inputPath, "utf-8");
|
||||||
|
const escaped = siteUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
const re = new RegExp(`\\]\\((${escaped}/[^)\\s]+)\\)`, "g");
|
||||||
|
let m;
|
||||||
|
while ((m = re.exec(content)) !== null) add(m[1]);
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
// Strip garden/* tags from a category list so they don't render as
|
// Strip garden/* tags from a category list so they don't render as
|
||||||
// plain category pills alongside the garden badge.
|
// plain category pills alongside the garden badge.
|
||||||
eleventyConfig.addFilter("withoutGardenTags", (categories) => {
|
eleventyConfig.addFilter("withoutGardenTags", (categories) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user