From 4a96aaac4c78b6e247b4bf607134fcd2397016ab Mon Sep 17 00:00:00 2001 From: John Dunning Date: Fri, 11 Oct 2024 13:59:12 -0700 Subject: [PATCH] Add RSS feed Add a link tag to the page metadata to make the RSS feed discoverable. Add @astrojs/rss package. --- astro.config.mjs | 5 +---- package-lock.json | 39 +++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/content/config.ts | 7 +++++++ src/layouts/Page.astro | 6 ++++++ src/pages/rss.xml.js | 35 +++++++++++++++++++++++++++++++++++ src/utils/dayjs.ts | 1 + 7 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 src/pages/rss.xml.js diff --git a/astro.config.mjs b/astro.config.mjs index bc6618d..de63eff 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -2,11 +2,8 @@ import { defineConfig } from "astro/config"; import icon from "astro-icon"; export default defineConfig({ - site: "https://www.sfcivictech.us", -// site: "https://www.sfcivictech.org", + site: "https://www.sfcivictech.org", base: "", - // site: "https://sfbrigade.github.io", - // base: "/astro-static-site/", trailingSlash: "ignore", compressHTML: false, integrations: [ diff --git a/package-lock.json b/package-lock.json index 8b3e56d..aaaa289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@astrojs/check": "^0.8.3", + "@astrojs/rss": "^4.0.8", "@iconify-json/fa": "^1.2.0", "@picocss/pico": "^2.0.6", "@stylistic/eslint-plugin": "^2.8.0", @@ -183,6 +184,16 @@ "node": "^18.17.1 || ^20.3.0 || >=21.0.0" } }, + "node_modules/@astrojs/rss": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.8.tgz", + "integrity": "sha512-63UYWS2xj00+N79JPiLeHiBBB3nHfjFlJ81T9a+UaYQ7aUZyvU0E9JdZeuONwHKKcSsvP+goPjWmoVifPToRGA==", + "license": "MIT", + "dependencies": { + "fast-xml-parser": "^4.5.0", + "kleur": "^4.1.5" + } + }, "node_modules/@astrojs/telemetry": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.1.0.tgz", @@ -4461,6 +4472,28 @@ "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -7988,6 +8021,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT" + }, "node_modules/style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", diff --git a/package.json b/package.json index b189ad3..2255ba1 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@astrojs/check": "^0.8.3", + "@astrojs/rss": "^4.0.8", "@iconify-json/fa": "^1.2.0", "@picocss/pico": "^2.0.6", "@stylistic/eslint-plugin": "^2.8.0", diff --git a/src/content/config.ts b/src/content/config.ts index 05c295e..abb92ed 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -16,6 +16,8 @@ const blog = defineCollection({ description: z.string(), date: z.string() .transform((value, ctx) => { + // we treat the date as a string instead of using the built-in date parsing + // so we can force it to be in PST, rather than the default UTC const date = parseYMD(value); if (!date.isValid()) { @@ -24,6 +26,11 @@ const blog = defineCollection({ message: "Invalid date", }); + // annoyingly, nothing about the file being validated here is available + // to this function. if we could get the filename, we could pull a + // missing date out of the name. given that we can't, we have to return + // undefined here and make any code that iterates over the collection + // call dateFromSlug(slug) for undefined dates, like rss.xml.js. return z.NEVER; } diff --git a/src/layouts/Page.astro b/src/layouts/Page.astro index 90216c5..9dd54e0 100644 --- a/src/layouts/Page.astro +++ b/src/layouts/Page.astro @@ -34,6 +34,12 @@ const baseURL = import.meta.env.BASE_URL.replace(/([^/])$/, "$1/"); + diff --git a/src/pages/rss.xml.js b/src/pages/rss.xml.js new file mode 100644 index 0000000..f7a3685 --- /dev/null +++ b/src/pages/rss.xml.js @@ -0,0 +1,35 @@ +import rss from "@astrojs/rss"; +import { getCollection } from "astro:content"; +import { dateFromSlug, formatRSSDate } from "@/utils"; + +export async function GET(context) +{ + const blog = await getCollection("blog"); + const items = blog.map((post) => { + const { + slug, + data: { + title, + descriptionHTML, + // pubDate is required, but some posts don't have a date specified, so + // create one from the filename + date = dateFromSlug(slug), + }, + } = post; + + return { + title, + description: descriptionHTML, + pubDate: formatRSSDate(date), + link: `/blog/${slug}`, + }; + }); + + return rss({ + title: "SF Civic Tech Blog", + description: "News from SF Civic Tech", + site: context.site, + trailingSlash: false, + items, + }); +} diff --git a/src/utils/dayjs.ts b/src/utils/dayjs.ts index 847eb5c..f830581 100644 --- a/src/utils/dayjs.ts +++ b/src/utils/dayjs.ts @@ -16,3 +16,4 @@ export default dayjs; export const parseYMD = (date: string) => dayjs(date, "YYYY-MM-DD", true); export const parseYMDToDate = (date: string) => parseYMD(date).toDate(); export const formatShortMDY = (date: Date) => date ? dayjs(date).format("MMM D, YYYY") : ""; +export const formatRSSDate = (date: Date) => date ? dayjs(date).format("ddd, D MMM YYYY HH:mm:ss ZZ") : "";