diff --git a/.gitignore b/.gitignore index 49d464e26..015821703 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ yarn.lock .vscode +# 忽略上游仓库 +ori \ No newline at end of file diff --git a/package.json b/package.json index 5852b9cad..1d2c5bd64 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "katex": "^0.16.22", "markdown-it": "^14.1.0", "mdast-util-to-string": "^4.0.0", + "node-html-parser": "^7.0.1", "overlayscrollbars": "^2.11.1", "photoswipe": "^5.4.4", "reading-time": "^1.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a08069cb9..08f2a9c1c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,9 @@ importers: mdast-util-to-string: specifier: ^4.0.0 version: 4.0.0 + node-html-parser: + specifier: ^7.0.1 + version: 7.0.1 overlayscrollbars: specifier: ^2.11.1 version: 2.11.1 @@ -2878,6 +2881,10 @@ packages: hastscript@9.0.1: 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: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} @@ -3601,6 +3608,9 @@ packages: encoding: optional: true + node-html-parser@7.0.1: + resolution: {integrity: sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==} + node-mock-http@1.0.0: resolution: {integrity: sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==} @@ -8410,6 +8420,8 @@ snapshots: property-information: 7.0.0 space-separated-tokens: 2.0.2 + he@1.2.0: {} + html-escaper@3.0.3: {} html-void-elements@3.0.0: {} @@ -9339,6 +9351,11 @@ snapshots: dependencies: 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.0: {} node-releases@2.0.19: {} diff --git a/public/favicon.webp b/public/favicon.webp new file mode 100644 index 000000000..f2c1aef37 Binary files /dev/null and b/public/favicon.webp differ diff --git a/src/config.ts b/src/config.ts index 672a563f1..c9ea96b6b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -49,7 +49,7 @@ export const siteConfig: SiteConfig = { favicon: [ // Leave this array empty to use the default favicon { - src: "https://q2.qlogo.cn/headimg_dl?dst_uin=2726730791&spec=5", // Path of the favicon, relative to the /public directory + src: "./favicon.webp", // Path of the favicon, relative to the /public directory // theme: 'light', // (Optional) Either 'light' or 'dark', set only if you have different favicons for light and dark mode // sizes: '32x32', // (Optional) Size of the favicon, set only if you have favicons of different sizes }, @@ -85,7 +85,7 @@ export const navBarConfig: NavBarConfig = { }; export const profileConfig: ProfileConfig = { - avatar: "https://q2.qlogo.cn/headimg_dl?dst_uin=2726730791&spec=5", // Relative to the /src directory. Relative to the /public directory if it starts with '/' + avatar: "/favicon.webp", // Relative to the /src directory. Relative to the /public directory if it starts with '/' name: "二叉树树", bio: "爱你所爱~ ❤", links: [ diff --git a/src/content/assets/images/2025-08-10-21-31-04-image.png b/src/content/assets/images/2025-08-10-21-31-04-image.png new file mode 100644 index 000000000..7a17eea2f Binary files /dev/null and b/src/content/assets/images/2025-08-10-21-31-04-image.png differ diff --git a/src/content/posts/folo-verification.md b/src/content/posts/folo-verification.md new file mode 100644 index 000000000..f946f67f5 --- /dev/null +++ b/src/content/posts/folo-verification.md @@ -0,0 +1,30 @@ +--- +title: 认证为Folo的创作者 +published: 2025-08-10T21:26:55 +description: 'Folo是一个RSS聚合平台,在上面不仅可以订阅很多优质的RSS源,也可以认领自己的RSS源' +image: '../assets/images/2025-08-10-21-31-04-image.png' +tags: [Folo] +category: '记录' +draft: false +lang: '' +--- + +# 这是啥? + +这是一篇Folo认证文章,用于认证本RSS归属于我 + +This message is used to verify that this feed (feedId:154118531036739584) belongs to me (userId:165740980096046080). Join me in enjoying the next generation information browser https://folo.is. + +# 我要怎么认证? + +首先 你得有一个博客,或者说一个有RSS的网站 + +比如 2x.nz/rss.xml + +接下来在Folo处点击认证 https://folo.is + +会弹出一个内容,要求让你放置到RSS + +最简单的方法就是像我这样新建一篇文章,然后原封不动粘贴这段话,然后认证即可 + +![](../assets/images/2025-08-10-21-31-04-image.png) \ No newline at end of file diff --git a/src/content/posts/pin.md b/src/content/posts/pin.md index 192de2f09..99a6e1013 100644 --- a/src/content/posts/pin.md +++ b/src/content/posts/pin.md @@ -18,6 +18,8 @@ QQ: 165624236 (易封,经常转生) TG: https://t.me/+blyHrp_GihhhMWVl +Discord: https://discord.gg/qaCDqsGA + Matrix: m.2x.nz [详细教程](/posts/element/) Microsoft Team: https://teams.live.com/l/community/FBA6DNy_XOUr-3vxwI?v=g1 \ No newline at end of file diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts index a3ae9c49b..d379cf800 100644 --- a/src/pages/rss.xml.ts +++ b/src/pages/rss.xml.ts @@ -1,33 +1,84 @@ -import { siteConfig } from "@/config"; -import rss from "@astrojs/rss"; -import { getSortedPosts } from "@utils/content-utils"; -import type { APIContext } from "astro"; -import MarkdownIt from "markdown-it"; -import sanitizeHtml from "sanitize-html"; +import rss from '@astrojs/rss'; +import sanitizeHtml from 'sanitize-html'; +import MarkdownIt from 'markdown-it'; +import { getCollection } from 'astro:content'; +import { siteConfig } from '@/config'; +import { parse as htmlParser } from 'node-html-parser'; +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(); + +// get dynamic import of images as a map collection +const imagesGlob = import.meta.glob<{ default: ImageMetadata }>( + '/src/content/**/*.{jpeg,jpg,png,gif,webp}', // include posts and assets +); 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({ title: siteConfig.title, - description: siteConfig.subtitle || "No description", - site: context.site ?? "https://fuwari.vercel.app", - items: blog.map((post) => { - const content = - typeof post.body === "string" ? post.body : String(post.body || ""); - - return { - title: post.data.title, - pubDate: post.data.published, - description: post.data.description || "", - link: `/posts/${post.slug}/`, - content: sanitizeHtml(parser.render(content), { - allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]), - }), - }; - }), + description: siteConfig.subtitle || 'No description', + site: context.site, + items: feed, customData: `${siteConfig.lang}`, }); }