From 9a0746a4715400ab8ef37ec23a4df93ae712e74b Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Wed, 22 Apr 2026 18:53:50 +0000 Subject: feat: add RSS feed generation and update package metadata - Implemented a new RSS feed generation feature in src/pages/feed.xml.ts, allowing users to follow blog updates. - Updated package.json and package-lock.json to include license information and new type definitions for markdown-it and sanitize-html. - Refactored createOgImage function to return Uint8Array instead of Buffer for better compatibility. - Simplified pageSchema by removing the optional mainEntityId parameter for cleaner schema generation. --- src/pages/feed.xml.js | 47 ---------------------------------------- src/pages/feed.xml.ts | 48 +++++++++++++++++++++++++++++++++++++++++ src/utils/createOgImage.ts | 2 +- src/utils/schemas/pageSchema.ts | 5 ++--- 4 files changed, 51 insertions(+), 51 deletions(-) delete mode 100644 src/pages/feed.xml.js create mode 100644 src/pages/feed.xml.ts (limited to 'src') diff --git a/src/pages/feed.xml.js b/src/pages/feed.xml.js deleted file mode 100644 index 005f2d8..0000000 --- a/src/pages/feed.xml.js +++ /dev/null @@ -1,47 +0,0 @@ -import { getCollection } from "astro:content"; -import MarkdownIt from "markdown-it"; -import rss from "@astrojs/rss"; -import sanitizeHtml from "sanitize-html"; -import { config } from "../config"; - -const parser = new MarkdownIt({ html: true, linkify: true }); - -export async function GET(context) { - const title = "RSS Feed | Valentin Popov Blog"; - const description = "Follow the latest posts from Valentin Popov via RSS."; - - const posts = (await getCollection("blog", ({ data }) => data.draft !== true)).sort((a, b) => b.data.datePublished.getTime() - a.data.datePublished.getTime()); - - const feedUrl = new URL("/feed.xml", context.site).toString(); - - return rss({ - title, - description, - site: context.site, - xmlns: { - atom: "http://www.w3.org/2005/Atom", - content: "http://purl.org/rss/1.0/modules/content/", - dc: "http://purl.org/dc/elements/1.1/", - }, - customData: [`en`, ``].join(""), - items: posts.map((post) => ({ - title: post.data.title, - description: post.data.description, - link: `/blog/${post.id}/`, - pubDate: post.data.datePublished, - author: `${config.author.email} (${config.author.name})`, - content: sanitizeHtml(parser.render(post.body ?? ""), { - allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img", "pre", "code", "span"]), - allowedAttributes: { - ...sanitizeHtml.defaults.allowedAttributes, - img: ["src", "alt", "title", "loading", "decoding"], - code: ["class"], - span: ["class", "style"], - pre: ["class", "style"], - a: ["href", "name", "target", "rel"], - }, - }), - customData: `${post.data.lang}`, - })), - }); -} diff --git a/src/pages/feed.xml.ts b/src/pages/feed.xml.ts new file mode 100644 index 0000000..9d0568f --- /dev/null +++ b/src/pages/feed.xml.ts @@ -0,0 +1,48 @@ +import type { APIContext } from "astro"; +import { getCollection } from "astro:content"; +import MarkdownIt from "markdown-it"; +import rss from "@astrojs/rss"; +import sanitizeHtml from "sanitize-html"; +import { config } from "../config"; + +const parser = new MarkdownIt({ html: false, linkify: true }); + +export async function GET(context: APIContext) { + const title = "RSS Feed | Valentin Popov Blog"; + const description = "Follow the latest posts from Valentin Popov via RSS."; + + const posts = (await getCollection("blog", ({ data }) => data.draft !== true)).sort((a, b) => b.data.datePublished.getTime() - a.data.datePublished.getTime()); + + const feedUrl = new URL("/feed.xml", context.site).toString(); + + return rss({ + title, + description, + site: context.site ?? config.author.url, + xmlns: { + atom: "http://www.w3.org/2005/Atom", + content: "http://purl.org/rss/1.0/modules/content/", + dc: "http://purl.org/dc/elements/1.1/", + }, + customData: ``, + items: posts.map((post) => ({ + title: post.data.title, + description: post.data.description, + link: `/blog/${post.id}/`, + pubDate: post.data.datePublished, + author: `${config.author.email} (${config.author.name})`, + content: sanitizeHtml(parser.render(post.body ?? ""), { + allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img", "pre", "code", "span"]), + allowedAttributes: { + ...sanitizeHtml.defaults.allowedAttributes, + img: ["src", "alt", "title", "loading", "decoding"], + code: ["class"], + span: ["class"], + pre: ["class"], + a: ["href", "name", "target", "rel"], + }, + }), + customData: `${post.data.lang}`, + })), + }); +} diff --git a/src/utils/createOgImage.ts b/src/utils/createOgImage.ts index da2cece..67731a7 100644 --- a/src/utils/createOgImage.ts +++ b/src/utils/createOgImage.ts @@ -5,7 +5,7 @@ import { Resvg } from "@resvg/resvg-js"; import dayjs from "dayjs"; import satori from "satori"; -export async function createOgImage(title: string, datePublished: Date): Promise { +export async function createOgImage(title: string, datePublished: Date): Promise { const formattedDate = dayjs(datePublished).format("MMMM DD, YYYY"); const markup = await satori( diff --git a/src/utils/schemas/pageSchema.ts b/src/utils/schemas/pageSchema.ts index ba8fd86..2305986 100644 --- a/src/utils/schemas/pageSchema.ts +++ b/src/utils/schemas/pageSchema.ts @@ -4,14 +4,13 @@ import { personId, websiteId } from "./ids"; export type WebsiteSchemaParams = { readonly description: string; readonly lang: string; - readonly mainEntityId?: string; readonly page: string; readonly siteUrl: string; readonly title: string; readonly type?: "WebPage" | "ProfilePage"; }; -export default ({ siteUrl, page, title, description, lang, type = "WebPage", mainEntityId }: WebsiteSchemaParams): WebPage | ProfilePage => { +export default ({ siteUrl, page, title, description, lang, type = "WebPage" }: WebsiteSchemaParams): WebPage | ProfilePage => { const url = new URL(page, siteUrl).toString(); const base = { @@ -28,7 +27,7 @@ export default ({ siteUrl, page, title, description, lang, type = "WebPage", mai return { ...base, "@type": "ProfilePage", - "mainEntity": { "@id": mainEntityId ?? personId(siteUrl) }, + "mainEntity": { "@id": personId(siteUrl) }, }; } -- cgit v1.2.3