fix: fixed html output for rss feeds 7aee73e2
Steve · 2025-10-05 16:55 4 file(s) · +83 −15
bun.lock +28 −0
13 13
        "@types/react": "^18.3.8",
14 14
        "@types/react-dom": "^18.3.0",
15 15
        "astro": "^4.15.7",
16 +
        "markdown-it": "^14.1.0",
16 17
        "react": "^18.3.1",
17 18
        "react-dom": "^18.3.1",
19 +
        "sanitize-html": "^2.17.0",
18 20
        "sharp": "^0.33.1",
19 21
      },
20 22
      "devDependencies": {
26 28
        "@tailwindcss/line-clamp": "^0.4.2",
27 29
        "@tailwindcss/typography": "^0.5.8",
28 30
        "@types/eslint": "^8.4.10",
31 +
        "@types/markdown-it": "^14.1.2",
29 32
        "@types/prettier": "^2.7.2",
33 +
        "@types/sanitize-html": "^2.16.0",
30 34
        "@types/sharp": "^0.31.1",
31 35
        "@typescript-eslint/eslint-plugin": "^5.48.0",
32 36
        "@typescript-eslint/parser": "^5.48.0",
377 381
378 382
    "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
379 383
384 +
    "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="],
385 +
386 +
    "@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="],
387 +
380 388
    "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
389 +
390 +
    "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="],
381 391
382 392
    "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
383 393
400 410
    "@types/react": ["@types/react@18.3.22", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q=="],
401 411
402 412
    "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
413 +
414 +
    "@types/sanitize-html": ["@types/sanitize-html@2.16.0", "", { "dependencies": { "htmlparser2": "^8.0.0" } }, "sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw=="],
403 415
404 416
    "@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="],
405 417
638 650
    "decode-named-character-reference": ["decode-named-character-reference@1.1.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w=="],
639 651
640 652
    "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
653 +
654 +
    "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
641 655
642 656
    "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
643 657
1077 1091
1078 1092
    "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
1079 1093
1094 +
    "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
1095 +
1080 1096
    "load-yaml-file": ["load-yaml-file@0.2.0", "", { "dependencies": { "graceful-fs": "^4.1.5", "js-yaml": "^3.13.0", "pify": "^4.0.1", "strip-bom": "^3.0.0" } }, "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw=="],
1081 1097
1082 1098
    "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
1104 1120
    "map-obj": ["map-obj@4.3.0", "", {}, "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ=="],
1105 1121
1106 1122
    "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
1123 +
1124 +
    "markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="],
1107 1125
1108 1126
    "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
1109 1127
1144 1162
    "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
1145 1163
1146 1164
    "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
1165 +
1166 +
    "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
1147 1167
1148 1168
    "meow": ["meow@9.0.0", "", { "dependencies": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", "decamelize": "^1.2.0", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", "normalize-package-data": "^3.0.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", "type-fest": "^0.18.0", "yargs-parser": "^20.2.3" } }, "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ=="],
1149 1169
1313 1333
1314 1334
    "parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="],
1315 1335
1336 +
    "parse-srcset": ["parse-srcset@1.0.2", "", {}, "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="],
1337 +
1316 1338
    "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
1317 1339
1318 1340
    "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
1382 1404
    "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
1383 1405
1384 1406
    "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
1407 +
1408 +
    "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
1385 1409
1386 1410
    "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
1387 1411
1478 1502
    "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
1479 1503
1480 1504
    "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
1505 +
1506 +
    "sanitize-html": ["sanitize-html@2.17.0", "", { "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", "htmlparser2": "^8.0.0", "is-plain-object": "^5.0.0", "parse-srcset": "^1.0.2", "postcss": "^8.3.11" } }, "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA=="],
1481 1507
1482 1508
    "sass-formatter": ["sass-formatter@0.7.9", "", { "dependencies": { "suf-log": "^2.5.3" } }, "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw=="],
1483 1509
1656 1682
    "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
1657 1683
1658 1684
    "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
1685 +
1686 +
    "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
1659 1687
1660 1688
    "ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="],
1661 1689
package.json +5 −1
11 11
		"format": "biome format --write src package.json"
12 12
	},
13 13
	"devDependencies": {
14 -
		"@biomejs/biome": "2.1.1",
15 14
		"@astrojs/mdx": "^3.1.6",
16 15
		"@astrojs/sitemap": "^3.1.6",
17 16
		"@astrojs/tailwind": "5.1.1",
17 +
		"@biomejs/biome": "2.1.1",
18 18
		"@tailwindcss/aspect-ratio": "^0.4.2",
19 19
		"@tailwindcss/line-clamp": "^0.4.2",
20 20
		"@tailwindcss/typography": "^0.5.8",
21 21
		"@types/eslint": "^8.4.10",
22 +
		"@types/markdown-it": "^14.1.2",
22 23
		"@types/prettier": "^2.7.2",
24 +
		"@types/sanitize-html": "^2.16.0",
23 25
		"@types/sharp": "^0.31.1",
24 26
		"@typescript-eslint/eslint-plugin": "^5.48.0",
25 27
		"@typescript-eslint/parser": "^5.48.0",
53 55
		"@types/react": "^18.3.8",
54 56
		"@types/react-dom": "^18.3.0",
55 57
		"astro": "^4.15.7",
58 +
		"markdown-it": "^14.1.0",
56 59
		"react": "^18.3.1",
57 60
		"react-dom": "^18.3.1",
61 +
		"sanitize-html": "^2.17.0",
58 62
		"sharp": "^0.33.1"
59 63
	}
60 64
}
src/pages/feed.xml.ts +25 −7
1 1
import rss from "@astrojs/rss";
2 2
import { getCollection } from "astro:content";
3 +
import { experimental_AstroContainer as AstroContainer } from "astro/container";
4 +
import { loadRenderers } from "astro:container";
5 +
import { getContainerRenderer as getMDXRenderer } from "@astrojs/mdx";
6 +
import sanitizeHtml from "sanitize-html";
3 7
import siteMeta from "@/site-config";
4 8
5 9
export async function GET() {
6 10
	const posts = await getCollection("post");
7 11
	const visiblePosts = posts.filter((post) => !post.data.hidden);
8 12
13 +
	const renderers = await loadRenderers([getMDXRenderer()]);
14 +
	const container = await AstroContainer.create({ renderers });
15 +
16 +
	const items = await Promise.all(
17 +
		visiblePosts.map(async (post) => {
18 +
			const { Content } = await post.render();
19 +
			const content = await container.renderToString(Content);
20 +
21 +
			return {
22 +
				title: post.data.title,
23 +
				description: post.data.description,
24 +
				pubDate: post.data.publishDate,
25 +
				link: `/posts/${post.slug}`,
26 +
				content: sanitizeHtml(content, {
27 +
					allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
28 +
				}),
29 +
			};
30 +
		}),
31 +
	);
32 +
9 33
	return rss({
10 34
		title: siteMeta.title,
11 35
		description: siteMeta.description,
12 36
		site: "https://stevedylan.dev",
13 -
		items: visiblePosts.map((post) => ({
14 -
			title: post.data.title,
15 -
			description: post.data.description,
16 -
			pubDate: post.data.publishDate,
17 -
			link: `/posts/${post.slug}`,
18 -
			content: post.body,
19 -
		})),
37 +
		items: items,
20 38
	});
21 39
}
src/pages/rss.xml.ts +25 −7
1 1
import rss from "@astrojs/rss";
2 2
import { getCollection } from "astro:content";
3 +
import { experimental_AstroContainer as AstroContainer } from "astro/container";
4 +
import { loadRenderers } from "astro:container";
5 +
import { getContainerRenderer as getMDXRenderer } from "@astrojs/mdx";
6 +
import sanitizeHtml from "sanitize-html";
3 7
import siteMeta from "@/site-config";
4 8
5 9
export async function GET() {
6 10
	const posts = await getCollection("post");
7 11
	const visiblePosts = posts.filter((post) => !post.data.hidden);
8 12
13 +
	const renderers = await loadRenderers([getMDXRenderer()]);
14 +
	const container = await AstroContainer.create({ renderers });
15 +
16 +
	const items = await Promise.all(
17 +
		visiblePosts.map(async (post) => {
18 +
			const { Content } = await post.render();
19 +
			const content = await container.renderToString(Content);
20 +
21 +
			return {
22 +
				title: post.data.title,
23 +
				description: post.data.description,
24 +
				pubDate: post.data.publishDate,
25 +
				link: `/posts/${post.slug}`,
26 +
				content: sanitizeHtml(content, {
27 +
					allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
28 +
				}),
29 +
			};
30 +
		}),
31 +
	);
32 +
9 33
	return rss({
10 34
		title: siteMeta.title,
11 35
		description: siteMeta.description,
12 36
		site: "https://stevedylan.dev",
13 -
		items: visiblePosts.map((post) => ({
14 -
			title: post.data.title,
15 -
			description: post.data.description,
16 -
			pubDate: post.data.publishDate,
17 -
			link: `/posts/${post.slug}`,
18 -
			content: post.body,
19 -
		})),
37 +
		items: items,
20 38
	});
21 39
}