summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json2
-rw-r--r--pnpm-lock.yaml58
-rw-r--r--src/loaders/pleroma.ts16
-rw-r--r--src/pages/rss.xml.ts23
-rw-r--r--src/pages/tags/[tag]/rss.xml.ts23
-rw-r--r--src/utils/markdown.ts11
6 files changed, 104 insertions, 29 deletions
diff --git a/package.json b/package.json
index df7f25c..4f25b06 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"rehype-external-links": "^3.0.0",
"rehype-unwrap-images": "^1.0.0",
"remark-directive": "^4.0.0",
+ "sanitize-html": "^2.17.0",
"satori": "0.15.2",
"satori-html": "^0.3.2",
"sharp": "^0.34.2",
@@ -54,6 +55,7 @@
"@tailwindcss/typography": "^0.5.16",
"@types/hast": "^3.0.4",
"@types/mdast": "^4.0.4",
+ "@types/sanitize-html": "^2.16.0",
"@types/turndown": "^5.0.5",
"autoprefixer": "^10.4.21",
"husky": "^9.1.7",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index db2b694..e7df8c8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -71,6 +71,9 @@ importers:
remark-directive:
specifier: ^4.0.0
version: 4.0.0
+ sanitize-html:
+ specifier: ^2.17.0
+ version: 2.17.0
satori:
specifier: 0.15.2
version: 0.15.2
@@ -114,6 +117,9 @@ importers:
'@types/mdast':
specifier: ^4.0.4
version: 4.0.4
+ '@types/sanitize-html':
+ specifier: ^2.16.0
+ version: 2.16.0
'@types/turndown':
specifier: ^5.0.5
version: 5.0.5
@@ -1265,6 +1271,9 @@ packages:
'@types/node@24.0.7':
resolution: {integrity: sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==}
+ '@types/sanitize-html@2.16.0':
+ resolution: {integrity: sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==}
+
'@types/sax@1.2.7':
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
@@ -1671,6 +1680,10 @@ packages:
decode-named-character-reference@1.1.0:
resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==}
+ deepmerge@4.3.1:
+ resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+ engines: {node: '>=0.10.0'}
+
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
@@ -1813,6 +1826,10 @@ packages:
escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
escape-string-regexp@5.0.0:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
@@ -2056,6 +2073,9 @@ packages:
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+ htmlparser2@8.0.2:
+ resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
+
htmlparser2@9.1.0:
resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
@@ -2129,6 +2149,10 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
+ is-plain-object@5.0.0:
+ resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
+ engines: {node: '>=0.10.0'}
+
is-wsl@3.1.0:
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
engines: {node: '>=16'}
@@ -2617,6 +2641,9 @@ packages:
parse-latin@7.0.0:
resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==}
+ parse-srcset@1.0.2:
+ resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
+
parse5-htmlparser2-tree-adapter@7.1.0:
resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
@@ -3071,6 +3098,9 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ sanitize-html@2.17.0:
+ resolution: {integrity: sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==}
+
sass-formatter@0.7.9:
resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==}
@@ -4663,6 +4693,10 @@ snapshots:
dependencies:
undici-types: 7.8.0
+ '@types/sanitize-html@2.16.0':
+ dependencies:
+ htmlparser2: 8.0.2
+
'@types/sax@1.2.7':
dependencies:
'@types/node': 17.0.45
@@ -5200,6 +5234,8 @@ snapshots:
dependencies:
character-entities: 2.0.2
+ deepmerge@4.3.1: {}
+
defu@6.1.4: {}
delayed-stream@1.0.0: {}
@@ -5379,6 +5415,8 @@ snapshots:
escape-html@1.0.3: {}
+ escape-string-regexp@4.0.0: {}
+
escape-string-regexp@5.0.0: {}
estree-util-attach-comments@3.0.0:
@@ -5747,6 +5785,13 @@ snapshots:
html-void-elements@3.0.0: {}
+ htmlparser2@8.0.2:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ entities: 4.5.0
+
htmlparser2@9.1.0:
dependencies:
domelementtype: 2.3.0
@@ -5801,6 +5846,8 @@ snapshots:
is-plain-obj@4.1.0: {}
+ is-plain-object@5.0.0: {}
+
is-wsl@3.1.0:
dependencies:
is-inside-container: 1.0.0
@@ -6545,6 +6592,8 @@ snapshots:
unist-util-visit-children: 3.0.0
vfile: 6.0.3
+ parse-srcset@1.0.2: {}
+
parse5-htmlparser2-tree-adapter@7.1.0:
dependencies:
domhandler: 5.0.3
@@ -7044,6 +7093,15 @@ snapshots:
safer-buffer@2.1.2: {}
+ sanitize-html@2.17.0:
+ dependencies:
+ deepmerge: 4.3.1
+ escape-string-regexp: 4.0.0
+ htmlparser2: 8.0.2
+ is-plain-object: 5.0.0
+ parse-srcset: 1.0.2
+ postcss: 8.5.6
+
sass-formatter@0.7.9:
dependencies:
suf-log: 2.5.3
diff --git a/src/loaders/pleroma.ts b/src/loaders/pleroma.ts
index 36ea4d6..83f60c2 100644
--- a/src/loaders/pleroma.ts
+++ b/src/loaders/pleroma.ts
@@ -1,6 +1,6 @@
import type { Loader } from "astro/loaders";
-import { marked } from "marked";
import TurndownService from "turndown";
+import { markdownToHtml } from "@/utils/markdown";
interface Logger {
info: (message: string) => void;
@@ -606,20 +606,6 @@ function replacePleromaLinks(
return modifiedContent;
}
-function markdownToHtml(markdown: string): string {
- // Configure marked options for safe rendering
- marked.setOptions({
- breaks: true, // Convert line breaks to <br>
- gfm: true, // GitHub flavored markdown
- });
-
- // Convert markdown to HTML
- const html = marked.parse(markdown);
-
- // Return as string (marked.parse can return string or Promise<string>)
- return typeof html === "string" ? html : "";
-}
-
function extractTitle(content: string): string {
// Extract first line or first sentence as title
const firstLine = content.split("\n")[0];
diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts
index d428cc9..61adea7 100644
--- a/src/pages/rss.xml.ts
+++ b/src/pages/rss.xml.ts
@@ -1,7 +1,9 @@
import rss from "@astrojs/rss";
import type { APIContext } from "astro";
+import sanitizeHtml from "sanitize-html";
import { getAllPosts } from "@/data/post";
import { siteConfig } from "@/site.config";
+import { markdownToHtml } from "@/utils/markdown";
export const GET = async (context: APIContext) => {
const posts = await getAllPosts();
@@ -10,13 +12,20 @@ export const GET = async (context: APIContext) => {
title: siteConfig.title,
description: siteConfig.description,
site: context.site || import.meta.env.SITE,
- items: posts.map((post) => ({
- title: post.data.title,
- description: post.data.description,
- pubDate: post.data.publishDate,
- link: `posts/${post.id}/`,
- author: post.data.author,
- })),
+ items: posts.map((post) => {
+ const htmlContent = post.rendered?.html || markdownToHtml(post.body || "");
+
+ return {
+ title: post.data.title,
+ description: post.data.description,
+ pubDate: post.data.publishDate,
+ link: `posts/${post.id}/`,
+ author: post.data.author,
+ content: sanitizeHtml(htmlContent, {
+ allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
+ }),
+ };
+ }),
customData: `<atom:link href="${context.site}rss.xml" rel="self" type="application/rss+xml" xmlns:atom="http://www.w3.org/2005/Atom" />`,
});
};
diff --git a/src/pages/tags/[tag]/rss.xml.ts b/src/pages/tags/[tag]/rss.xml.ts
index 3a61414..89e158c 100644
--- a/src/pages/tags/[tag]/rss.xml.ts
+++ b/src/pages/tags/[tag]/rss.xml.ts
@@ -1,7 +1,9 @@
import rss from "@astrojs/rss";
import type { APIContext } from "astro";
+import sanitizeHtml from "sanitize-html";
import { getAllPosts, getUniqueTags } from "@/data/post";
import { siteConfig } from "@/site.config";
+import { markdownToHtml } from "@/utils/markdown";
export async function getStaticPaths() {
// Get all posts (including archived, now includes pleroma too)
@@ -37,13 +39,20 @@ export const GET = async (context: APIContext) => {
title: `${siteConfig.title} - ${tag}`,
description: `Posts tagged with ${tag}`,
site,
- items: sortedPosts.map((post) => ({
- title: post.data.title,
- description: post.data.description,
- pubDate: post.data.publishDate,
- link: `posts/${post.id}/`,
- author: post.data.author,
- })),
+ items: sortedPosts.map((post) => {
+ const htmlContent = post.rendered?.html || markdownToHtml(post.body || "");
+
+ return {
+ title: post.data.title,
+ description: post.data.description,
+ pubDate: post.data.publishDate,
+ link: `posts/${post.id}/`,
+ author: post.data.author,
+ content: sanitizeHtml(htmlContent, {
+ allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
+ }),
+ };
+ }),
customData: `<atom:link href="${site}tags/${tag}/rss.xml" rel="self" type="application/rss+xml" xmlns:atom="http://www.w3.org/2005/Atom" />`,
});
};
diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts
new file mode 100644
index 0000000..946d8d8
--- /dev/null
+++ b/src/utils/markdown.ts
@@ -0,0 +1,11 @@
+import { marked } from "marked";
+
+marked.setOptions({
+ breaks: true,
+ gfm: true,
+});
+
+export function markdownToHtml(markdown: string): string {
+ const html = marked.parse(markdown);
+ return typeof html === "string" ? html : "";
+}