feat: added rss feed for articles

This commit is contained in:
Sergey Yarkov 2022-08-18 08:21:55 +03:00
parent 2550e807d3
commit e2c3cd4779
5 changed files with 111 additions and 12 deletions

View File

@ -14,6 +14,7 @@ Personal website created with Astro.
- [x] Multilang web-site - [x] Multilang web-site
- [x] Form handling via [Netlify Forms](https://www.netlify.com/products/forms/) - [x] Form handling via [Netlify Forms](https://www.netlify.com/products/forms/)
- [x] Comment system via [utterances](https://utteranc.es/) - [x] Comment system via [utterances](https://utteranc.es/)
- [x] RSS Feed for articles
- [ ] SEO optimized - [ ] SEO optimized
## License ## License

52
package-lock.json generated
View File

@ -8,6 +8,9 @@
"name": "yarkov.tech", "name": "yarkov.tech",
"version": "0.0.1", "version": "0.0.1",
"license": "MIT", "license": "MIT",
"dependencies": {
"@astrojs/rss": "^1.0.0"
},
"devDependencies": { "devDependencies": {
"@astrojs/image": "^0.3.4", "@astrojs/image": "^0.3.4",
"@astrojs/mdx": "^0.10.0", "@astrojs/mdx": "^0.10.0",
@ -183,6 +186,14 @@
"node": "^14.18.0 || >=16.12.0" "node": "^14.18.0 || >=16.12.0"
} }
}, },
"node_modules/@astrojs/rss": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-1.0.0.tgz",
"integrity": "sha512-vOPocf/SC0TfHsCwQXupvLGm0y3oIwpRCnnF2igDP3gFSxQEOk4nmsuVTEU2N87hYyX1/5uxbjJhixuSV/L8Cw==",
"dependencies": {
"fast-xml-parser": "^4.0.8"
}
},
"node_modules/@astrojs/solid-js": { "node_modules/@astrojs/solid-js": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@astrojs/solid-js/-/solid-js-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@astrojs/solid-js/-/solid-js-1.0.0.tgz",
@ -3558,6 +3569,21 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true "dev": true
}, },
"node_modules/fast-xml-parser": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.9.tgz",
"integrity": "sha512-4G8EzDg2Nb1Qurs3f7BpFV4+jpMVsdgLVuG1Uv8O2OHJfVCg7gcA53obuKbmVqzd4Y7YXVBK05oJG7hzGIdyzg==",
"dependencies": {
"strnum": "^1.0.5"
},
"bin": {
"fxparser": "src/cli/cli.js"
},
"funding": {
"type": "paypal",
"url": "https://paypal.me/naturalintelligence"
}
},
"node_modules/fastq": { "node_modules/fastq": {
"version": "1.13.0", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
@ -8146,6 +8172,11 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/strnum": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
},
"node_modules/style-to-object": { "node_modules/style-to-object": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",
@ -9335,6 +9366,14 @@
"prismjs": "^1.28.0" "prismjs": "^1.28.0"
} }
}, },
"@astrojs/rss": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-1.0.0.tgz",
"integrity": "sha512-vOPocf/SC0TfHsCwQXupvLGm0y3oIwpRCnnF2igDP3gFSxQEOk4nmsuVTEU2N87hYyX1/5uxbjJhixuSV/L8Cw==",
"requires": {
"fast-xml-parser": "^4.0.8"
}
},
"@astrojs/solid-js": { "@astrojs/solid-js": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@astrojs/solid-js/-/solid-js-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@astrojs/solid-js/-/solid-js-1.0.0.tgz",
@ -11761,6 +11800,14 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true "dev": true
}, },
"fast-xml-parser": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.9.tgz",
"integrity": "sha512-4G8EzDg2Nb1Qurs3f7BpFV4+jpMVsdgLVuG1Uv8O2OHJfVCg7gcA53obuKbmVqzd4Y7YXVBK05oJG7hzGIdyzg==",
"requires": {
"strnum": "^1.0.5"
}
},
"fastq": { "fastq": {
"version": "1.13.0", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
@ -14979,6 +15026,11 @@
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true "dev": true
}, },
"strnum": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
},
"style-to-object": { "style-to-object": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",

View File

@ -21,6 +21,7 @@
"devDependencies": { "devDependencies": {
"@astrojs/image": "^0.3.4", "@astrojs/image": "^0.3.4",
"@astrojs/mdx": "^0.10.0", "@astrojs/mdx": "^0.10.0",
"@astrojs/rss": "^1.0.0",
"@astrojs/solid-js": "^1.0.0", "@astrojs/solid-js": "^1.0.0",
"@types/mdx": "^2.0.2", "@types/mdx": "^2.0.2",
"@typescript-eslint/eslint-plugin": "^5.33.1", "@typescript-eslint/eslint-plugin": "^5.33.1",

View File

@ -3,11 +3,10 @@ import * as utils from "@utils/url";
import { DEFAULT_LANGUAGE } from "@root/config"; import { DEFAULT_LANGUAGE } from "@root/config";
import languages from "./languages"; import languages from "./languages";
export function getTranslatedArticles(Astro: Readonly<AstroGlobal>): MDXInstance<ArticleType>[] { type TranslatedArticlesType = Record<string, Record<LanguageKeys, MDXInstance<ArticleType>> | Record<string, never>>;
const data = import.meta.glob<MDXInstance<ArticleType>>("../../content/articles/**/*.mdx", { eager: true });
const modules: Record<string, Record<string, MDXInstance<ArticleType>>> = {}; export function formatArticlesByLangs(data: Record<string, MDXInstance<ArticleType>>): TranslatedArticlesType {
const siteLang = utils.getLanguageFromURL(Astro.url.pathname); const modules: TranslatedArticlesType = {};
const articles: MDXInstance<ArticleType>[] = [];
for (const module of Object.values(data)) { for (const module of Object.values(data)) {
const splitted = module.file.split("/"); const splitted = module.file.split("/");
@ -17,6 +16,15 @@ export function getTranslatedArticles(Astro: Readonly<AstroGlobal>): MDXInstance
modules[name][lang] = module; modules[name][lang] = module;
} }
return modules;
}
export function getTranslatedArticles(Astro: Readonly<AstroGlobal>): MDXInstance<ArticleType>[] {
const data = import.meta.glob<MDXInstance<ArticleType>>("../../content/articles/**/*.mdx", { eager: true });
const modules = formatArticlesByLangs(data);
const siteLang = utils.getLanguageFromURL(Astro.url.pathname);
const articles: MDXInstance<ArticleType>[] = [];
for (const key of Object.keys(modules)) { for (const key of Object.keys(modules)) {
const langs = Object.keys(modules[key]); const langs = Object.keys(modules[key]);
for (const articleLang of langs) { for (const articleLang of langs) {
@ -53,11 +61,13 @@ function transformExports<T>(modules: Record<string, { default: T }>) {
const translations = transformExports<UIDictionaryKeys>(import.meta.glob("./*/ui.ts", { eager: true })); const translations = transformExports<UIDictionaryKeys>(import.meta.glob("./*/ui.ts", { eager: true }));
export function useTranslation(Astro: Readonly<AstroGlobal>): (key: UIDictionaryKeys) => string | undefined { export function translate(key: UIDictionaryKeys, lang: LanguageKeys): string | undefined {
const lang = utils.getLanguageFromURL(Astro.url.pathname);
return function translate(key: UIDictionaryKeys): string | undefined {
const string: string | undefined = translations[lang]?.[key] || translations[DEFAULT_LANGUAGE][key]; const string: string | undefined = translations[lang]?.[key] || translations[DEFAULT_LANGUAGE][key];
if (string === undefined) console.log(`Cannot find any string for translation key "${key}" on "${Astro.url.pathname}" page.`); if (string === undefined) console.log(`Cannot find any string for translation key "${key}".`);
return string; return string;
}; }
export function useTranslation(Astro: Readonly<AstroGlobal>): (key: UIDictionaryKeys) => string | undefined {
const lang: LanguageKeys = utils.getLanguageFromURL(Astro.url.pathname) as LanguageKeys;
return (key: UIDictionaryKeys) => translate(key, lang);
} }

35
src/pages/rss.xml.ts Normal file
View File

@ -0,0 +1,35 @@
import type { MDXInstance } from "astro";
import rss from "@astrojs/rss";
import { DEFAULT_LANGUAGE, SITE_METADATA } from "@root/config";
import * as utils from "@i18n/utils";
const data = import.meta.glob<MDXInstance<ArticleType>>("../../content/articles/**/*.mdx", { eager: true });
const articles = Object.values(utils.formatArticlesByLangs(data));
const items = [];
for (const key of Object.keys(articles)) {
const article: Record<string, MDXInstance<ArticleType>> = articles[key];
for (const lang of Object.keys(article)) {
const { title, description, slug, published_at, coverImage } = article[lang].frontmatter;
items.push({
link:
`${import.meta.env.SITE}blog/` +
new Date(published_at).toLocaleDateString("en-CA") +
`${lang !== DEFAULT_LANGUAGE ? "/" + lang : ""}` +
`/${slug}/`,
title,
description: `${description}${coverImage !== undefined ? `<img src="${coverImage}" alt="${title}">` : ""}`,
pubDate: published_at,
customData: `<language>${lang}</language>`,
});
}
}
export const get = () =>
rss({
title: SITE_METADATA.name,
description: utils.translate("site.description", "ru"),
site: import.meta.env.SITE,
items,
});