feat: markdown links render in preview

This commit is contained in:
venus
2026-10-20 16:25:13 -05:00
parent efb0fbea94
commit 963521b3a9
6 changed files with 79 additions and 8 deletions
+9
View File
@@ -1,9 +1,17 @@
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import markdownIt from "markdown-it";
/** /**
* Custom Eleventy Filters for River Rooks Blog * Custom Eleventy Filters for River Rooks Blog
*/ */
const excerptMarkdown = markdownIt({ html: false });
export const renderExcerptMarkdown = (excerpt) => {
if (typeof excerpt !== "string") return "";
return excerptMarkdown.renderInline(excerpt);
};
export const postDate = (dateObj) => { export const postDate = (dateObj) => {
if (!dateObj) return ""; if (!dateObj) return "";
const date = typeof dateObj === "string" ? new Date(dateObj) : dateObj; const date = typeof dateObj === "string" ? new Date(dateObj) : dateObj;
@@ -92,6 +100,7 @@ export const removeTerminalPreview = (htmlContent) => {
}; };
export const filters = { export const filters = {
renderExcerptMarkdown,
postDate, postDate,
isoDate, isoDate,
limit, limit,
+1 -1
View File
@@ -29,7 +29,7 @@
</h2> </h2>
<p class="featured-prose"> <p class="featured-prose">
{{ featured.data.excerpt }} {{ featured.data.excerpt | renderExcerptMarkdown | safe }}
</p> </p>
{% if featured.data.terminal == true %} {% if featured.data.terminal == true %}
+1 -1
View File
@@ -11,7 +11,7 @@
</h3> </h3>
{% if post.data.excerpt %} {% if post.data.excerpt %}
<p class="feed-excerpt">{{ post.data.excerpt }}</p> <p class="feed-excerpt">{{ post.data.excerpt | renderExcerptMarkdown | safe }}</p>
{% endif %} {% endif %}
<div class="feed-tags-row"> <div class="feed-tags-row">
+64
View File
@@ -0,0 +1,64 @@
import test from "node:test";
import assert from "node:assert/strict";
import nunjucks from "nunjucks";
import path from "node:path";
import { filters } from "../src/_filters/index.js";
const includesDir = path.resolve("src/_includes");
const markdownExcerpt = "Read the [deployment guide](/guides/deploy/) before continuing.";
function renderTemplate(template, context) {
const env = new nunjucks.Environment(new nunjucks.FileSystemLoader(includesDir), { autoescape: false });
for (const [name, filter] of Object.entries(filters)) {
env.addFilter(name, filter);
}
return env.render(template, context);
}
test("Excerpt Markdown: renders Markdown links as safe inline HTML", () => {
assert.equal(
filters.renderExcerptMarkdown(markdownExcerpt),
'Read the <a href="/guides/deploy/">deployment guide</a> before continuing.'
);
});
test("Excerpt Markdown: renders links in post-card previews", () => {
const output = renderTemplate("partials/post-card.njk", {
post: {
fileSlug: "example-dispatch",
url: "/dispatches/example/",
date: "2026-10-06",
data: {
title: "Example dispatch",
excerpt: markdownExcerpt,
tags: ["systems"]
},
templateContent: "A short article."
}
});
assert.match(output, /<p class="feed-excerpt">\s*Read the <a href="\/guides\/deploy\/">deployment guide<\/a> before continuing\.\s*<\/p>/);
});
test("Excerpt Markdown: renders links in featured previews and escapes raw HTML", () => {
const output = renderTemplate("partials/featured-card.njk", {
collections: {
posts: [{
url: "/dispatches/example/",
date: "2026-10-06",
data: {
featured: true,
tags: ["systems"],
title: "Example dispatch",
excerpt: `${markdownExcerpt} <script>alert('unsafe')</script>`
},
templateContent: ""
}]
}
});
assert.match(output, /<a href="\/guides\/deploy\/">deployment guide<\/a>/);
assert.match(output, /&lt;script&gt;alert\('unsafe'\)&lt;\/script&gt;/);
assert.doesNotMatch(output, /<script>/);
});
+3 -1
View File
@@ -7,7 +7,8 @@ import {
filterTagList, filterTagList,
isoDate, isoDate,
postDate, postDate,
readingTime readingTime,
renderExcerptMarkdown
} from "../src/_filters/index.js"; } from "../src/_filters/index.js";
const includesDir = path.resolve("src/_includes"); const includesDir = path.resolve("src/_includes");
@@ -19,6 +20,7 @@ function renderFeaturedCard(terminal) {
env.addFilter("isoDate", isoDate); env.addFilter("isoDate", isoDate);
env.addFilter("postDate", postDate); env.addFilter("postDate", postDate);
env.addFilter("readingTime", readingTime); env.addFilter("readingTime", readingTime);
env.addFilter("renderExcerptMarkdown", renderExcerptMarkdown);
const featured = { const featured = {
url: "/dispatches/example/", url: "/dispatches/example/",
+1 -5
View File
@@ -41,11 +41,7 @@ test("Site Config: correctly parses SITE_DOMAIN and derives service subdomains",
const gitService = site.services.find((s) => s.name === "Gitea"); const gitService = site.services.find((s) => s.name === "Gitea");
assert.ok(gitService, "Gitea service exists"); assert.ok(gitService, "Gitea service exists");
assert.equal(gitService.url, "https://git.riverrooks.dev/venus"); assert.equal(gitService.url, "https://git.riverrooks.dev/venus");
assert.equal(gitService.tag, "git.testcluster.internal:2222"); assert.equal(gitService.tag, "git.testcluster.internal:443");
const chefService = site.services.find((s) => s.name === "CyberChef");
assert.ok(chefService, "CyberChef service exists");
assert.equal(chefService.url, "https://chef.testcluster.internal");
// Verify navigation links // Verify navigation links
const navHome = site.nav.find((n) => n.label === "Home"); const navHome = site.nav.find((n) => n.label === "Home");