mirror of
https://github.com/Ad-closeNN/blog-fuwari.git
synced 2026-05-31 01:40:03 -04:00
使用新版 RSS 构建器
https://billyle.dev/posts/adding-rss-feed-content-and-fixing-markdown-image-paths-in-astro https://github.com/afoim/fuwari/blob/main/src/pages/rss.xml.ts
This commit is contained in:
@@ -41,6 +41,7 @@
|
|||||||
"katex": "^0.16.22",
|
"katex": "^0.16.22",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
|
"node-html-parser": "^7.0.1",
|
||||||
"overlayscrollbars": "^2.11.4",
|
"overlayscrollbars": "^2.11.4",
|
||||||
"pagefind": "^1.3.0",
|
"pagefind": "^1.3.0",
|
||||||
"photoswipe": "^5.4.4",
|
"photoswipe": "^5.4.4",
|
||||||
|
|||||||
Generated
+20
-9
@@ -83,6 +83,9 @@ importers:
|
|||||||
mdast-util-to-string:
|
mdast-util-to-string:
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
|
node-html-parser:
|
||||||
|
specifier: ^7.0.1
|
||||||
|
version: 7.0.1
|
||||||
overlayscrollbars:
|
overlayscrollbars:
|
||||||
specifier: ^2.11.4
|
specifier: ^2.11.4
|
||||||
version: 2.11.4
|
version: 2.11.4
|
||||||
@@ -2353,10 +2356,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
|
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
|
||||||
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||||
|
|
||||||
css-what@6.1.0:
|
|
||||||
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
|
|
||||||
css-what@6.2.2:
|
css-what@6.2.2:
|
||||||
resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
|
resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -2951,6 +2950,10 @@ packages:
|
|||||||
hastscript@9.0.1:
|
hastscript@9.0.1:
|
||||||
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
|
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
|
||||||
|
|
||||||
|
he@1.2.0:
|
||||||
|
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
html-escaper@3.0.3:
|
html-escaper@3.0.3:
|
||||||
resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
|
resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
|
||||||
|
|
||||||
@@ -3678,6 +3681,9 @@ packages:
|
|||||||
encoding:
|
encoding:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
node-html-parser@7.0.1:
|
||||||
|
resolution: {integrity: sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==}
|
||||||
|
|
||||||
node-mock-http@1.0.2:
|
node-mock-http@1.0.2:
|
||||||
resolution: {integrity: sha512-zWaamgDUdo9SSLw47we78+zYw/bDr5gH8pH7oRRs8V3KmBtu8GLgGIbV2p/gRPd3LWpEOpjQj7X1FOU3VFMJ8g==}
|
resolution: {integrity: sha512-zWaamgDUdo9SSLw47we78+zYw/bDr5gH8pH7oRRs8V3KmBtu8GLgGIbV2p/gRPd3LWpEOpjQj7X1FOU3VFMJ8g==}
|
||||||
|
|
||||||
@@ -7628,7 +7634,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
boolbase: 1.0.0
|
boolbase: 1.0.0
|
||||||
css-select: 5.1.0
|
css-select: 5.1.0
|
||||||
css-what: 6.1.0
|
css-what: 6.2.2
|
||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
domhandler: 5.0.3
|
domhandler: 5.0.3
|
||||||
domutils: 3.2.2
|
domutils: 3.2.2
|
||||||
@@ -7774,7 +7780,7 @@ snapshots:
|
|||||||
css-select@5.1.0:
|
css-select@5.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
boolbase: 1.0.0
|
boolbase: 1.0.0
|
||||||
css-what: 6.1.0
|
css-what: 6.2.2
|
||||||
domhandler: 5.0.3
|
domhandler: 5.0.3
|
||||||
domutils: 3.2.2
|
domutils: 3.2.2
|
||||||
nth-check: 2.1.1
|
nth-check: 2.1.1
|
||||||
@@ -7801,8 +7807,6 @@ snapshots:
|
|||||||
mdn-data: 2.12.2
|
mdn-data: 2.12.2
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
css-what@6.1.0: {}
|
|
||||||
|
|
||||||
css-what@6.2.2: {}
|
css-what@6.2.2: {}
|
||||||
|
|
||||||
cssesc@3.0.0: {}
|
cssesc@3.0.0: {}
|
||||||
@@ -8582,6 +8586,8 @@ snapshots:
|
|||||||
property-information: 7.0.0
|
property-information: 7.0.0
|
||||||
space-separated-tokens: 2.0.2
|
space-separated-tokens: 2.0.2
|
||||||
|
|
||||||
|
he@1.2.0: {}
|
||||||
|
|
||||||
html-escaper@3.0.3: {}
|
html-escaper@3.0.3: {}
|
||||||
|
|
||||||
html-void-elements@3.0.0: {}
|
html-void-elements@3.0.0: {}
|
||||||
@@ -9513,6 +9519,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
whatwg-url: 5.0.0
|
whatwg-url: 5.0.0
|
||||||
|
|
||||||
|
node-html-parser@7.0.1:
|
||||||
|
dependencies:
|
||||||
|
css-select: 5.1.0
|
||||||
|
he: 1.2.0
|
||||||
|
|
||||||
node-mock-http@1.0.2: {}
|
node-mock-http@1.0.2: {}
|
||||||
|
|
||||||
node-releases@2.0.19: {}
|
node-releases@2.0.19: {}
|
||||||
@@ -10781,7 +10792,7 @@ snapshots:
|
|||||||
commander: 7.2.0
|
commander: 7.2.0
|
||||||
css-select: 5.1.0
|
css-select: 5.1.0
|
||||||
css-tree: 2.3.1
|
css-tree: 2.3.1
|
||||||
css-what: 6.1.0
|
css-what: 6.2.2
|
||||||
csso: 5.0.5
|
csso: 5.0.5
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
|
|
||||||
|
|||||||
+74
-33
@@ -1,43 +1,84 @@
|
|||||||
import rss from "@astrojs/rss";
|
import rss from '@astrojs/rss';
|
||||||
import { getSortedPosts } from "@utils/content-utils";
|
import sanitizeHtml from 'sanitize-html';
|
||||||
import { url } from "@utils/url-utils";
|
import MarkdownIt from 'markdown-it';
|
||||||
import type { APIContext } from "astro";
|
import { getCollection } from 'astro:content';
|
||||||
import MarkdownIt from "markdown-it";
|
import { siteConfig } from '@/config';
|
||||||
import sanitizeHtml from "sanitize-html";
|
import { parse as htmlParser } from 'node-html-parser';
|
||||||
import { siteConfig } from "@/config";
|
import { getImage } from 'astro:assets';
|
||||||
|
import type { APIContext, ImageMetadata } from 'astro';
|
||||||
|
import type { RSSFeedItem } from '@astrojs/rss';
|
||||||
|
import { getSortedPosts } from '@/utils/content-utils';
|
||||||
|
|
||||||
const parser = new MarkdownIt();
|
const markdownParser = new MarkdownIt();
|
||||||
|
|
||||||
function stripInvalidXmlChars(str: string): string {
|
// get dynamic import of images as a map collection
|
||||||
return str.replace(
|
const imagesGlob = import.meta.glob<{ default: ImageMetadata }>(
|
||||||
// biome-ignore lint/suspicious/noControlCharactersInRegex: https://www.w3.org/TR/xml/#charsets
|
'/src/content/**/*.{jpeg,jpg,png,gif,webp}', // include posts and assets
|
||||||
/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFDD0-\uFDEF\uFFFE\uFFFF]/g,
|
);
|
||||||
"",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(context: APIContext) {
|
export async function GET(context: APIContext) {
|
||||||
const blog = await getSortedPosts();
|
if (!context.site) {
|
||||||
|
throw Error('site not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the same ordering as site listing (pinned first, then by published desc)
|
||||||
|
const posts = await getSortedPosts();
|
||||||
|
const feed: RSSFeedItem[] = [];
|
||||||
|
|
||||||
|
for (const post of posts) {
|
||||||
|
// convert markdown to html string
|
||||||
|
const body = markdownParser.render(post.body);
|
||||||
|
// convert html string to DOM-like structure
|
||||||
|
const html = htmlParser.parse(body);
|
||||||
|
// hold all img tags in variable images
|
||||||
|
const images = html.querySelectorAll('img');
|
||||||
|
|
||||||
|
for (const img of images) {
|
||||||
|
const src = img.getAttribute('src');
|
||||||
|
if (!src) continue;
|
||||||
|
|
||||||
|
// Handle content-relative images and convert them to built _astro paths
|
||||||
|
if (src.startsWith('./') || src.startsWith('../')) {
|
||||||
|
let importPath: string | null = null;
|
||||||
|
|
||||||
|
if (src.startsWith('./')) {
|
||||||
|
// Path relative to the post file directory
|
||||||
|
const prefixRemoved = src.slice(2);
|
||||||
|
importPath = `/src/content/posts/${prefixRemoved}`;
|
||||||
|
} else {
|
||||||
|
// Path like ../assets/images/xxx -> relative to /src/content/
|
||||||
|
const cleaned = src.replace(/^\.\.\//, '');
|
||||||
|
importPath = `/src/content/${cleaned}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageMod = await imagesGlob[importPath]?.()?.then((res) => res.default);
|
||||||
|
if (imageMod) {
|
||||||
|
const optimizedImg = await getImage({ src: imageMod });
|
||||||
|
img.setAttribute('src', new URL(optimizedImg.src, context.site).href);
|
||||||
|
}
|
||||||
|
} else if (src.startsWith('/')) {
|
||||||
|
// images starting with `/` are in public dir
|
||||||
|
img.setAttribute('src', new URL(src, context.site).href);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
feed.push({
|
||||||
|
title: post.data.title,
|
||||||
|
description: post.data.description,
|
||||||
|
pubDate: post.data.published,
|
||||||
|
link: `/posts/${post.slug}/`,
|
||||||
|
// sanitize the new html string with corrected image paths
|
||||||
|
content: sanitizeHtml(html.toString(), {
|
||||||
|
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return rss({
|
return rss({
|
||||||
title: siteConfig.title,
|
title: siteConfig.title,
|
||||||
description: siteConfig.subtitle || "No description",
|
description: siteConfig.subtitle || 'No description',
|
||||||
site: context.site ?? "https://fuwari.vercel.app",
|
site: context.site,
|
||||||
items: blog.map((post) => {
|
items: feed,
|
||||||
const content =
|
|
||||||
typeof post.body === "string" ? post.body : String(post.body || "");
|
|
||||||
const cleanedContent = stripInvalidXmlChars(content);
|
|
||||||
return {
|
|
||||||
title: post.data.title,
|
|
||||||
pubDate: post.data.published,
|
|
||||||
description: post.data.description || "",
|
|
||||||
link: url(`/posts/${post.slug}/`),
|
|
||||||
content: sanitizeHtml(parser.render(cleanedContent), {
|
|
||||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
|
|
||||||
// 准备迎接我的 Folo 之验证吧!——鲁迅 not 达摩(bushi
|
// 准备迎接我的 Folo 之验证吧!——鲁迅 not 达摩(bushi
|
||||||
customData: `<language>${siteConfig.lang}</language><follow_challenge><feedId>177350379949135872</feedId><userId>83370505718413312</userId></follow_challenge>`,
|
customData: `<language>${siteConfig.lang}</language><follow_challenge><feedId>177350379949135872</feedId><userId>83370505718413312</userId></follow_challenge>`,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user