rss
An RSS 2.0 feed: its items in the order given (newest first, as readers expect).
import type { FeedItem } from "#codebase/tcmts/content"; export function rss(feed: { title: string; /** the site's address, in full: the items' relative links are from it */ link: string; description: string; /** its self link, for readers: the feed's own address */ self?: string; language?: string; items: FeedItem[]; }): string { const esc = (s: string) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]!); const day = (date: string) => new Date(/^\d{4}-\d{2}-\d{2}$/.test(date) ? `${date}T00:00:00Z` : date).toUTCString(); const absolute = (html: string) => html.replace( /(href|src)="(?!#|[a-z][a-z0-9+.-]*:|\/\/)([^"]*)"/gi, (_, attr: string, to: string) => `${attr}="${new URL(to, feed.link).href}"`, ); const items = feed.items.map((item) => [ "<item>", `<title>${esc(item.title)}</title>`, `<link>${esc(item.link)}</link>`, `<guid isPermaLink="true">${esc(item.link)}</guid>`, `<pubDate>${day(item.date)}</pubDate>`, ...(item.author ? [`<dc:creator>${esc(item.author)}</dc:creator>`] : []), ...(item.html !== undefined ? [`<description>${esc(absolute(item.html))}</description>`] : []), "</item>", ].join("\n"), ); return [ '<?xml version="1.0" encoding="utf-8"?>', '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">', "<channel>", `<title>${esc(feed.title)}</title>`, `<link>${esc(feed.link)}</link>`, ...(feed.self ? [`<atom:link href="${esc(feed.self)}" rel="self" type="application/rss+xml"/>`] : []), `<description>${esc(feed.description)}</description>`, ...(feed.language ? [`<language>${esc(feed.language)}</language>`] : []), ...(feed.items[0] ? [`<lastBuildDate>${day(feed.items[0].date)}</lastBuildDate>`] : []), ...items, "</channel>", "</rss>", "", ].join("\n"); }