Skip to content

Commit aca9977

Browse files
CopilotSF-Zhou
andauthored
Add RSS feed generation to static blog build (#227)
* Initial plan * Add RSS feed generation support Co-authored-by: SF-Zhou <7477599+SF-Zhou@users.noreply.github.com> * Improve RSS generation with better error handling Co-authored-by: SF-Zhou <7477599+SF-Zhou@users.noreply.github.com> * Fix RSS date validation and use proper author element Co-authored-by: SF-Zhou <7477599+SF-Zhou@users.noreply.github.com> * Add RSS feed documentation to README Co-authored-by: SF-Zhou <7477599+SF-Zhou@users.noreply.github.com> * fix categoreis --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SF-Zhou <7477599+SF-Zhou@users.noreply.github.com> Co-authored-by: SF-Zhou <sfzhou.scut@gmail.com>
1 parent 51940a9 commit aca9977

File tree

3 files changed

+71
-0
lines changed

3 files changed

+71
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ npm install
1313
# generate site
1414
npm run build
1515
```
16+
17+
## RSS Feed
18+
19+
The build process automatically generates an RSS feed at `public/rss.xml` containing the latest 20 articles. The RSS feed URL is: https://sf-zhou.github.io/rss.xml

config.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{
22
"site_name": "SF-Zhou's Blog",
3+
"site_url": "https://sf-zhou.github.io",
4+
"site_description": "SF-Zhou's Blog",
5+
"site_language": "zh-CN",
36
"posts_path": "posts",
47
"output_path": "public",
58
"profile_path": "profile",

src/main.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,65 @@ async function write_when_change(file_path, new_content) {
2020
await fs.writeFile(file_path, new_content);
2121
}
2222

23+
function escapeXml(unsafe) {
24+
return unsafe
25+
.replace(/&/g, "&amp;")
26+
.replace(/</g, "&lt;")
27+
.replace(/>/g, "&gt;")
28+
.replace(/"/g, "&quot;")
29+
.replace(/'/g, "&apos;");
30+
}
31+
32+
function generateRSS(articles, config) {
33+
const now = new Date().toUTCString();
34+
35+
const items = articles.slice(0, 20).map(article => {
36+
// Parse date safely - format is YYYY.MM.DD
37+
let pubDate;
38+
try {
39+
const dateStr = article.date.replace(/\./g, '-');
40+
const date = new Date(dateStr);
41+
if (isNaN(date.getTime())) {
42+
pubDate = now;
43+
} else {
44+
pubDate = date.toUTCString();
45+
}
46+
} catch (e) {
47+
pubDate = now;
48+
}
49+
50+
const link = `${config.site_url}${article.url_path}`;
51+
const guid = link;
52+
const author = article.author || config.default_author || '';
53+
const tags = Array.isArray(article.tags) ? article.tags.map(t => String(t)).join(', ') : '';
54+
const categoryTags = article.tags.map(t => `<category>${escapeXml(String(t))}</category>`).join('\n ');
55+
56+
return ` <item>
57+
<title>${escapeXml(article.title)}</title>
58+
<link>${escapeXml(link)}</link>
59+
<guid isPermaLink="true">${escapeXml(guid)}</guid>
60+
<pubDate>${pubDate}</pubDate>
61+
<dc:creator>${escapeXml(author)}</dc:creator>
62+
${categoryTags}
63+
</item>`;
64+
}).join('\n');
65+
66+
const language = config.site_language || 'zh-CN';
67+
68+
return `<?xml version="1.0" encoding="UTF-8"?>
69+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
70+
<channel>
71+
<title>${escapeXml(config.site_name)}</title>
72+
<link>${escapeXml(config.site_url)}</link>
73+
<description>${escapeXml(config.site_description)}</description>
74+
<language>${language}</language>
75+
<lastBuildDate>${now}</lastBuildDate>
76+
<atom:link href="${escapeXml(config.site_url)}/rss.xml" rel="self" type="application/rss+xml" />
77+
${items}
78+
</channel>
79+
</rss>`;
80+
}
81+
2382
async function main() {
2483
const config = JSON.parse(await fs.readFile('./config.json'));
2584

@@ -148,6 +207,11 @@ async function main() {
148207
const json_path = join(config.output_path, 'index.json');
149208
await write_when_change(json_path, JSON.stringify(articles_info, null, 2));
150209

210+
// Generate RSS feed
211+
const rss_content = generateRSS(articles_info, config);
212+
const rss_path = join(config.output_path, 'rss.xml');
213+
await write_when_change(rss_path, rss_content);
214+
151215
const profile_template_name = './src/profile.md';
152216
const profile_template = (await fs.readFile(profile_template_name)).toString();
153217
const profile = mustache.render(profile_template, { articles: articles_info.slice(0, 5) });

0 commit comments

Comments
 (0)