From 73152773ccdbdd7c1072125f55a3b39077063ec9 Mon Sep 17 00:00:00 2001 From: klemek Date: Sun, 26 Apr 2026 11:06:20 +0200 Subject: [PATCH] feat: RSS feed --- bun.lock | 7 ++++ index.html | 3 ++ package.json | 1 + post-build.ts | 65 ++++++++++++++++++++++++++++------- src/components/PageFooter.vue | 7 ++-- src/lib/meta.ts | 1 + 6 files changed, 69 insertions(+), 15 deletions(-) diff --git a/bun.lock b/bun.lock index 6d8c757..9ee6995 100644 --- a/bun.lock +++ b/bun.lock @@ -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=="], diff --git a/index.html b/index.html index 7c40100..0e77f25 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,9 @@ + + + %VITE_CUSTOM_HEAD% diff --git a/package.json b/package.json index 2642fa8..65540cf 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/post-build.ts b/post-build.ts index a76f64f..1223fa9 100644 --- a/post-build.ts +++ b/post-build.ts @@ -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 | null { const metadata: Record = { 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, baseHtml: string): return outHtml } +function addFeedArticle(metadata: Record, 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()) diff --git a/src/components/PageFooter.vue b/src/components/PageFooter.vue index ea920cd..d664a1b 100644 --- a/src/components/PageFooter.vue +++ b/src/components/PageFooter.vue @@ -1,5 +1,5 @@ diff --git a/src/lib/meta.ts b/src/lib/meta.ts index 14f5645..6c29096 100644 --- a/src/lib/meta.ts +++ b/src/lib/meta.ts @@ -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