feat: RSS feed

This commit is contained in:
2026-04-26 11:06:20 +02:00
parent f918488963
commit 73152773cc
6 changed files with 69 additions and 15 deletions
+7
View File
@@ -22,6 +22,7 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-oxlint": "~1.60.0",
"eslint-plugin-vue": "~10.8.0",
"feed": "^5.2.1",
"jiti": "^2.6.1",
"npm-run-all2": "^8.0.4",
"oxlint": "~1.60.0",
@@ -622,6 +623,8 @@
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"feed": ["feed@5.2.1", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-jTynzYPWs9ALjro0GW8j7sv9y7cJBeOdD4Y88kVqYy/eyusIX3g+499JiTDIlD9Ge/unebx57T4Uzo6vpYvMtA=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
@@ -900,6 +903,8 @@
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.99.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ipuOv1R2K4MHeuCEAZGpuUbAgma4gb0sdacyrTjJtMOy/OY9UvWfVlwErdB09KIkp4fPDpQJDJfvYN6bC8jeNg=="],
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
@@ -1006,6 +1011,8 @@
"wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
"xml-js": ["xml-js@1.6.11", "", { "dependencies": { "sax": "^1.2.4" }, "bin": { "xml-js": "./bin/cli.js" } }, "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g=="],
"xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
+3
View File
@@ -8,6 +8,9 @@
<link rel="stylesheet" type="text/css" href="/src/style.scss" />
<meta property="og:url" content="%VITE_BASE_URL%" />
<meta property="og:title" content="%VITE_APP_TITLE_NO_HTML%" />
<link rel="alternate" href="%VITE_BASE_URL%/rss" type="application/rss+xml" title="RSS 2.0" />
<link rel="alternate" href="%VITE_BASE_URL%/atom.xml" type="application/atom+xml" title="Atom 2.0" />
<link rel="alternate" href="%VITE_BASE_URL%/feed.json" type="application/json" title="JSON Feed 1.0"/>
%VITE_CUSTOM_HEAD%
</head>
<body>
+1
View File
@@ -34,6 +34,7 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-oxlint": "~1.60.0",
"eslint-plugin-vue": "~10.8.0",
"feed": "^5.2.1",
"jiti": "^2.6.1",
"npm-run-all2": "^8.0.4",
"oxlint": "~1.60.0",
+52 -11
View File
@@ -1,5 +1,6 @@
import fs from 'fs'
import process from 'process'
import { Feed } from 'feed'
process.loadEnvFile()
@@ -28,6 +29,10 @@ function readArticleMetadata(path: string): Record<string, string> | null {
const metadata: Record<string, string> = {
path: path.replaceAll('/index.md', ''),
}
const dateMatch = path.match(/(\d{4}\/\d{2}\/\d{2})/)
if (dateMatch && dateMatch[1]) {
metadata['date'] = dateMatch[1].replaceAll('/', '-')
}
do {
subMatch = METADATA_REGEX.exec(match[1])
if (subMatch && subMatch[1] && subMatch[2]) {
@@ -69,6 +74,18 @@ function formatArticlePage(metadata: Record<string, string>, baseHtml: string):
return outHtml
}
function addFeedArticle(metadata: Record<string, string>, feed: Feed) {
if (metadata.draft !== 'true') {
feed.addItem({
title: metadata.title.replace(/(<([^>]+)>)/gi, '').trim(),
id: metadata.path,
link: `${process.env.VITE_BASE_URL}${metadata.path}/`,
date: new Date(Date.parse(metadata.date)),
image: metadata.thumbnail.replace('./', process.env.VITE_BASE_URL + metadata.path + '/'),
})
}
}
const indexContent = fs.readFileSync('dist/index.html', { encoding: 'utf8' })
if (!indexContent) {
@@ -76,15 +93,39 @@ if (!indexContent) {
process.exit(1)
}
getFiles('articles')
.filter((path) => path.match(/index.md$/))
.forEach((path) => {
const metadata = readArticleMetadata(path)
if (metadata) {
fs.writeFileSync(
`dist/${metadata.path}/index.html`,
formatArticlePage(metadata, indexContent),
)
console.info(`Wrote dist/${metadata.path}/index.html`)
}
const metadatas = getFiles('articles')
.filter((path) => path.match(/\d{4}\/\d{2}\/\d{2}\/index.md$/))
.map((path) => readArticleMetadata(path))
.filter((metadata) => !!metadata)
const feed = new Feed({
title: process.env.VITE_APP_TITLE?.replace(/(<([^>]+)>)/gi, '').trim() ?? '',
id: process.env.VITE_BASE_URL,
link: process.env.VITE_BASE_URL,
language: process.env.VITE_APP_LANG,
favicon: process.env.VITE_BASE_URL + '/articles/favicon.ico',
generator: 'md-blog',
feedLinks: {
json: process.env.VITE_BASE_URL + 'feed.json',
atom: process.env.VITE_BASE_URL + 'atom.xml',
rss: process.env.VITE_BASE_URL + 'rss',
},
updated: new Date(
Math.max(
0,
...metadatas
.filter((metadata) => metadata.draft !== 'true')
.map((metadata) => Date.parse(metadata.date)),
),
),
})
metadatas.forEach((metadata) => {
fs.writeFileSync(`dist/${metadata.path}/index.html`, formatArticlePage(metadata, indexContent))
console.info(`Wrote dist/${metadata.path}/index.html`)
addFeedArticle(metadata, feed)
})
fs.writeFileSync('dist/feed.json', feed.json1())
fs.writeFileSync('dist/atom.xml', feed.atom1())
fs.writeFileSync('dist/rss', feed.rss2())
+4 -3
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { REPOSITORY, NAME, VERSION } from '@/lib/meta'
import { REPOSITORY, NAME, VERSION, BASE_URL } from '@/lib/meta'
</script>
<template>
@@ -9,8 +9,9 @@ import { REPOSITORY, NAME, VERSION } from '@/lib/meta'
<hr />
<footer>
<small>
{{ new Date().getFullYear() }} - Made with <a :href="REPOSITORY">{{ NAME }}</a>
{{ VERSION }}
{{ new Date().getFullYear() }} Made with
<a :href="REPOSITORY">{{ NAME }} {{ VERSION }}</a>
<a :href="BASE_URL + 'atom.xml'"><i icon="rss"></i> RSS</a>
</small>
</footer>
</template>
+1
View File
@@ -5,3 +5,4 @@ export const VERSION = packageJson.version
export const REPOSITORY = packageJson.repository
export const TITLE = import.meta.env.VITE_APP_TITLE
export const SIGNATURE = import.meta.env.VITE_APP_SIGNATURE
export const BASE_URL = import.meta.env.BASE_URL