Skip to content

Commit 0230e11

Browse files
authored
Add RSS feed (#379)
1 parent a52bcd6 commit 0230e11

File tree

10 files changed

+190
-10
lines changed

10 files changed

+190
-10
lines changed

astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export default defineConfig({
77
base: "",
88
integrations: [mdx(), solidJs()],
99
output: "static",
10+
site: "https://maplibre.org/",
1011
});

package-lock.json

Lines changed: 41 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@
1414
},
1515
"dependencies": {
1616
"@astrojs/mdx": "^4.0.2",
17+
"@astrojs/rss": "^4.0.11",
1718
"@astrojs/solid-js": "^5.0.0",
1819
"@fontsource/alata": "^5.1.1",
1920
"@igor.dvlpr/astro-post-excerpt": "^3.0.4",
2021
"astro": "^5.1.0",
2122
"bootstrap": "^5.3.3",
2223
"maplibre-gl": "^5.0.0",
2324
"sass": "1.76.0",
24-
"solid-js": "^1.9.3"
25+
"solid-js": "^1.9.3",
26+
"ultrahtml": "^1.5.3"
2527
},
2628
"devDependencies": {
2729
"@types/node": "^22.10.2",

public/img/icon/rss.svg

Lines changed: 11 additions & 0 deletions
Loading

src/components/Footer.astro

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import { Image } from "astro:assets";
33
import maplibreLogo from "../../public/img/maplibre-logo.svg";
4+
import rssIcon from "../../public/img/icon/rss.svg";
45
import bskyIcon from "../../public/img/icon/bsky.svg";
56
import mstdnIcon from "../../public/img/icon/mstdn.svg";
67
import xIcon from "../../public/img/icon/x.svg";
@@ -21,6 +22,13 @@ import ghIcon from "../../public/img/icon/gh.svg";
2122

2223
<div class="col-8">
2324
<div class="nav justify-content-end">
25+
<a
26+
class="nav-link text-white"
27+
href={new URL("news/index.xml", Astro.site)}
28+
aria-label="MapLibre RSS Feed"
29+
>
30+
<Image src={rssIcon} alt="RSS icon" height={16} width={16} />
31+
</a>
2432
<a
2533
class="nav-link text-white"
2634
href="https://bsky.app/profile/maplibre.org"

src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const SITE_TITLE = "MapLibre";
2+
export const SITE_DESCRIPTION =
3+
"The MapLibre Organization is an umbrella for open-source mapping libraries.";

src/layouts/Layout.astro

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import "../styles/bootstrap.scss";
33
import Footer from "../components/Footer.astro";
44
import Nav from "../components/Nav.astro";
5+
import { SITE_TITLE } from "../constants";
56
const { title, description } = Astro.props;
67
// import { ClientRouter } from 'astro:transitions';
78
---
@@ -70,8 +71,15 @@ const { title, description } = Astro.props;
7071
crossorigin
7172
/>
7273

74+
<link
75+
rel="alternate"
76+
type="application/rss+xml"
77+
title={SITE_TITLE}
78+
href={new URL("news/index.xml", Astro.site)}
79+
/>
80+
7381
<title>
74-
{title ? `${title} | MapLibre` : "MapLibre"}
82+
{title ? `${title} | ${SITE_TITLE}` : SITE_TITLE}
7583
</title>
7684
</head>
7785
<body>

src/pages/index.astro

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ import { Image } from "astro:assets";
33
import maplibreLogo from "../../public/img/maplibre-logo.svg";
44
import Layout from "../layouts/Layout.astro";
55
import { LandingPageMap } from "../components/LandingPageMap";
6-
7-
const title = "MapLibre";
8-
const description =
9-
"The MapLibre Organization is an umbrella for open-source mapping libraries.";
6+
import { SITE_DESCRIPTION } from "../constants";
107
---
118

129
<Layout>
@@ -27,8 +24,7 @@ const description =
2724
<p
2825
style="word-break:normal; margin-top:30px; max-width: 800px;"
2926
>
30-
The MapLibre Organization is an umbrella for open-source
31-
mapping libraries.
27+
{SITE_DESCRIPTION}
3228
</p>
3329

3430
<!-- <p>Open-source map libraries for web and mobile.</p> -->

src/pages/news/[slug].xml.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import type { APIContext } from "astro";
2+
import rss, { type RSSFeedItem } from "@astrojs/rss";
3+
import {
4+
getCollection,
5+
getEntry,
6+
render,
7+
type CollectionEntry,
8+
} from "astro:content";
9+
import { experimental_AstroContainer as AstroContainer } from "astro/container";
10+
import mdxRenderer from "@astrojs/mdx/server.js";
11+
import solidRenderer from "@astrojs/solid-js/server.js";
12+
13+
import { transform, walk } from "ultrahtml";
14+
import sanitize from "ultrahtml/transformers/sanitize";
15+
16+
import { SITE_TITLE, SITE_DESCRIPTION } from "../../constants";
17+
18+
// need to make this endpoint dynamic with only one path
19+
// otherwise the astro file will take precedence
20+
export function getStaticPaths() {
21+
return [{ params: { slug: "index" } }];
22+
}
23+
24+
const container = await AstroContainer.create({});
25+
container.addServerRenderer({ renderer: mdxRenderer, name: "mdx" });
26+
container.addServerRenderer({ renderer: solidRenderer, name: "solid" });
27+
28+
const getAuthors = async (authors: string[]): Promise<string> => {
29+
const authorEntries = await Promise.all(
30+
authors.map(async (author) => await getEntry("authors", author)),
31+
);
32+
33+
const authorTitles = authorEntries
34+
.map((author) => author?.data?.title)
35+
.filter(Boolean);
36+
37+
if (authorTitles.length > 1) {
38+
const lastAuthor = authorTitles.pop();
39+
return `${authorTitles.join(", ")} and ${lastAuthor}`;
40+
}
41+
42+
return authorTitles.join(", ");
43+
};
44+
45+
const getPost = async (
46+
context: APIContext,
47+
post: CollectionEntry<"news">,
48+
): Promise<RSSFeedItem> => {
49+
const content = await transform(
50+
await container.renderToString((await render(post)).Content),
51+
[
52+
async (node) => {
53+
await walk(node, async (node) => {
54+
if (node.name === "img" && node.attributes.src?.startsWith("/")) {
55+
node.attributes.src = context.site + node.attributes.src.slice(1);
56+
}
57+
});
58+
return node;
59+
},
60+
sanitize({
61+
dropElements: ["script", "style"],
62+
}),
63+
],
64+
);
65+
66+
return {
67+
title: post.data.title,
68+
pubDate: post.data.date,
69+
description: post.data.description,
70+
author: await getAuthors(post.data.authors),
71+
content,
72+
link: `/news/${post.id}/`,
73+
};
74+
};
75+
76+
export async function GET(context: APIContext) {
77+
const news = await getCollection("news");
78+
const items = news.sort((a, b) => +b.data.date - +a.data.date).slice(0, 15);
79+
80+
return rss({
81+
title: SITE_TITLE,
82+
description: SITE_DESCRIPTION,
83+
site: context.site || "",
84+
items: await Promise.all(items.map(async (post) => getPost(context, post))),
85+
customData: `<language>en-US</language><lastBuildDate>${new Date().toUTCString()}</lastBuildDate>`,
86+
});
87+
}

src/pages/news/index.astro

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
---
22
import { getCollection } from "astro:content";
3+
import { Image } from "astro:assets";
34
import Layout from "../../layouts/Layout.astro";
45
import { render } from "astro:content";
56
import TitleHeader from "../../components/TitleHeader.astro";
67
import PostExcerpt from "@igor.dvlpr/astro-post-excerpt";
8+
import iconRss from "../../../public/img/icon/rss.svg";
79
810
const posts = (await getCollection("news")).sort((a, b) => {
911
console.log(b.data);
@@ -26,10 +28,32 @@ const posts = (await getCollection("news")).sort((a, b) => {
2628
}
2729
}
2830
}
31+
32+
.badge {
33+
display: block;
34+
width: max-content;
35+
margin: auto;
36+
margin-top: 12px;
37+
padding-bottom: 3px;
38+
39+
:global(img) {
40+
top: -2px;
41+
position: relative;
42+
}
43+
}
2944
</style>
3045

3146
<Layout title="News">
32-
<TitleHeader>News</TitleHeader>
47+
<TitleHeader>
48+
News
49+
<span class="badge bg-primary text-white">
50+
<a href={new URL("news/index.xml", Astro.site)}>
51+
<Image src={iconRss} alt="MapLibre RSS feed" height={16} width={16} />
52+
RSS
53+
</a>
54+
</span>
55+
</TitleHeader>
56+
3357
<div class="container">
3458
<div class="row justify-content-center">
3559
<div class="col-md-10 col-lg-8">

0 commit comments

Comments
 (0)