aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKumar Damani <me@kumardamani.net>2026-08-03 17:23:49 +0000
committerKumar Damani <me@kumardamani.net>2026-08-03 17:23:49 +0000
commit84aff405b134337ad335453f339f46bd86e7bf27 (patch)
tree4557e915ae1e2656e2c6967f46fa457a98c31fee
parentba451a6b9ba283b4c519283a45aa523662854677 (diff)
added atom feed
-rw-r--r--feed.typ63
-rw-r--r--global/common.typ3
-rw-r--r--main.typ3
3 files changed, 61 insertions, 8 deletions
diff --git a/feed.typ b/feed.typ
index 75639e0..3a5fd21 100644
--- a/feed.typ
+++ b/feed.typ
@@ -1,5 +1,6 @@
-// RSS 2.0 feed for the blog posts, emitted as feed.xml by main.typ.
-// Items are built from the metadata in site-info.pages (newest first).
+// RSS 2.0 and Atom feeds for the blog posts, emitted as feed.xml and
+// atom.xml by main.typ. Entries are built from the metadata in
+// site-info.pages (newest first).
#import "/global/common.typ": baseurl
@@ -13,16 +14,25 @@
)
}
-#let feed-xml(site-info) = {
+// ISO date ("2026-07-28") to RFC 3339 ("2026-07-28T00:00:00Z").
+#let rfc3339(iso) = iso + "T00:00:00Z"
+
+#let _entries(site-info, item-fn) = {
let pages = site-info.pages
let ids = pages.keys().filter(k => k.starts-with("post/"))
- let items = ()
+ let entries = ()
for id in ids {
let p = pages.at(id)
let title = if "long-title" in p { p.long-title } else { p.title }
let url = baseurl + "/" + id + ".html"
+ entries.push(item-fn(title, url, p))
+ }
+ entries
+}
+#let feed-xml(site-info) = {
+ let items = _entries(site-info, (title, url, p) => {
let lines = (
" <item>",
" <title>" + xml-escape(title) + "</title>",
@@ -36,8 +46,8 @@
lines.push(" <description>" + xml-escape(p.description) + "</description>")
}
lines.push(" </item>")
- items.push(lines.join("\n"))
- }
+ lines.join("\n")
+ })
(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
@@ -52,3 +62,44 @@
"",
).join("\n")
}
+
+// Atom (RFC 4287) feed, emitted as atom.xml by main.typ.
+#let atom-xml(site-info) = {
+ let entries = _entries(site-info, (title, url, p) => {
+ let lines = (
+ " <entry>",
+ " <title>" + xml-escape(title) + "</title>",
+ " <link href=\"" + url + "\"/>",
+ " <id>" + url + "</id>",
+ )
+ if "date" in p {
+ lines.push(" <updated>" + rfc3339(p.date) + "</updated>")
+ }
+ if "description" in p {
+ lines.push(" <summary>" + xml-escape(p.description) + "</summary>")
+ }
+ lines.push(" </entry>")
+ lines.join("\n")
+ })
+
+ // newest post first, so its date is the feed's last-update time
+ let updated = rfc3339(site-info.pages.at(
+ site-info.pages.keys().filter(k => k.starts-with("post/")).first()
+ ).date)
+
+ (
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
+ "<feed xmlns=\"http://www.w3.org/2005/Atom\">",
+ " <title>" + xml-escape(site-info.title) + "</title>",
+ " <link href=\"" + baseurl + "/\"/>",
+ " <link rel=\"self\" href=\"" + baseurl + "/atom.xml\"/>",
+ " <id>" + baseurl + "/</id>",
+ " <updated>" + updated + "</updated>",
+ " <author>",
+ " <name>Kumar</name>",
+ " </author>",
+ entries.join("\n"),
+ "</feed>",
+ "",
+ ).join("\n")
+}
diff --git a/global/common.typ b/global/common.typ
index d1a0aaa..269fc62 100644
--- a/global/common.typ
+++ b/global/common.typ
@@ -185,6 +185,7 @@
html.elem("link", attrs: (rel: "preload", href: base + "extlink.svg", "as": "image"))
html.link(rel: "stylesheet", href: base + "main.css")
html.link(rel: "alternate", type: "application/rss+xml", title: site + " RSS", href: base + "feed.xml")
+ html.link(rel: "alternate", type: "application/atom+xml", title: site + " Atom", href: base + "atom.xml")
for ss in stylesheets {
html.link(rel: "stylesheet", href: base + ss)
@@ -231,7 +232,7 @@
html.footer({
html.span(class: "updated")[\[Updated: #ci.date.\]]
html.span(class: "logs")[
- \[For nerds: #extlink(srcurl)[site source], #extlink(ci.link)[logs], #link(base + "feed.xml")[rss]\]
+ \[For nerds: #extlink(srcurl)[site source], #extlink(ci.link)[logs], #link(base + "feed.xml")[rss], #link(base + "atom.xml")[atom]\]
]
})
})
diff --git a/main.typ b/main.typ
index e74b903..8eea0e2 100644
--- a/main.typ
+++ b/main.typ
@@ -4,7 +4,7 @@
#import "/global/common.typ": *
#import "/site.typ": site-info
-#import "/feed.typ": feed-xml
+#import "/feed.typ": feed-xml, atom-xml
#import "/index.typ" as index-page
#import "/software.typ" as software-page
@@ -76,6 +76,7 @@
#asset("favicon.ico", read("/global/favicon.ico", encoding: none))
#asset("favicon.svg", read("/global/favicon.svg", encoding: none))
#asset("feed.xml", bytes(feed-xml(site-info)))
+#asset("atom.xml", bytes(atom-xml(site-info)))
// image assets collected from pages via the img/fig helpers
#context {