"name": "juplo",
"version": "0.0.1",
"dependencies": {
+ "@astrojs/rss": "^4.0.18",
"astro": "^6.4.4"
},
"devDependencies": {
"node": ">=22.12.0"
}
},
+ "node_modules/@astrojs/rss": {
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.18.tgz",
+ "integrity": "sha512-wc5DwKlbTEdgVAWnHy8krFTeQ42t1v/DJqeq5HtulYK3FYHE4krtRGjoyhS3eXXgfdV6Raoz2RU3wrMTFAitRg==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-xml-parser": "^5.5.7",
+ "piccolore": "^0.1.3",
+ "zod": "^4.3.6"
+ }
+ },
"node_modules/@astrojs/telemetry": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.2.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
+ "node_modules/@nodable/entities": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.1.tgz",
+ "integrity": "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodable"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/@oslojs/encoding": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz",
"fast-string-width": "^3.0.2"
}
},
+ "node_modules/fast-xml-builder": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz",
+ "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "path-expression-matcher": "^1.5.0",
+ "xml-naming": "^0.1.0"
+ }
+ },
+ "node_modules/fast-xml-parser": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.8.0.tgz",
+ "integrity": "sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@nodable/entities": "^2.1.0",
+ "fast-xml-builder": "^1.2.0",
+ "path-expression-matcher": "^1.5.0",
+ "strnum": "^2.3.0",
+ "xml-naming": "^0.1.0"
+ },
+ "bin": {
+ "fxparser": "src/cli/cli.js"
+ }
+ },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
+ "node_modules/path-expression-matcher": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz",
+ "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/piccolore": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz",
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/strnum": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz",
+ "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/svgo": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz",
"node": ">=4"
}
},
+ "node_modules/xml-naming": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz",
+ "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/xxhash-wasm": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz",
"astro": "astro"
},
"dependencies": {
+ "@astrojs/rss": "^4.0.18",
"astro": "^6.4.4"
},
"devDependencies": {
---
-import { getCollection } from 'astro:content';
import type { CollectionEntry } from 'astro:content';
+import { getAllPosts, postUrl } from '../lib/posts';
interface Props {
currentUrl: string;
const { currentUrl } = Astro.props;
-const allPosts = (await getCollection('blog', ({ data }) => !data.draft))
- .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
+const allPosts = await getAllPosts();
// Group posts by year, years sorted descending
const postsByYear: Record<string, CollectionEntry<'blog'>[]> = {};
return currentUrl.startsWith(s.url);
})?.name ?? '';
-// Helper: get post url
-function postUrl(post: CollectionEntry<'blog'>) {
- return post.data.url ?? `/${post.id}/`;
-}
-
// Determine active year from currentUrl
function isActiveYear(year: string) {
return currentUrl === `/blog/archive/${year}/` ||
---
interface Props {
title: string;
+ description?: string;
canonical?: string;
lang?: string;
}
-const { title, canonical, lang = 'en-US' } = Astro.props;
+const { title, description, canonical, lang = 'en-US' } = Astro.props;
const siteTitle = 'juplo';
const fullTitle = title === siteTitle ? siteTitle : `${title} | ${siteTitle}`;
const resolvedCanonical = canonical ?? new URL(Astro.url.pathname, Astro.site).toString();
<script>try{document.documentElement.dataset.layout=localStorage.getItem('layout')||'classic'}catch(e){}</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{fullTitle}</title>
+ {description && <meta name="description" content={description} />}
+ <meta property="og:title" content={fullTitle} />
+ {description && <meta property="og:description" content={description} />}
+ <meta property="og:type" content="website" />
+ <meta property="og:url" content={resolvedCanonical} />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" media="only screen" type="text/css" href="/css/screen.css">
<link rel="stylesheet" media="only screen" type="text/css" href="/css/resurrection.css">
--- /dev/null
+import { getCollection } from 'astro:content';
+
+export async function getAllPosts() {
+ return (await getCollection('blog', ({ data }) => !data.draft))
+ .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
+}
+
+export function postUrl(post: { id: string; data: { url?: string } }): string {
+ return post.data.url ?? `/${post.id}/`;
+}
+
+export function getSummary(body: string): string {
+ let text = body
+ .replace(/```[\s\S]*?```/g, '')
+ .replace(/`[^`]*`/g, '')
+ .replace(/<[^>]+>/g, '')
+ .replace(/\{\{[^}]*\}\}/g, '')
+ .replace(/!\[[^\]]*\]\([^)]*\)/g, '')
+ .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
+ .replace(/^#{1,6}\s+/gm, '')
+ .replace(/[*_]{1,3}([^*_\n]+)[*_]{1,3}/g, '$1');
+ const paras = text.split(/\n{2,}/).map(p => p.trim().replace(/\n/g, ' ')).filter(p => p.length > 20);
+ return paras[0]?.substring(0, 350) ?? '';
+}
+
+export function wordCount(body: string): number {
+ return body.replace(/```[\s\S]*?```/g, '').split(/\s+/).filter(w => w.length > 0).length;
+}
---
-import { getCollection, render } from 'astro:content';
+import { render } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
import Breadcrumb from '../components/Breadcrumb.astro';
import BlogNav from '../components/BlogNav.astro';
import TaxonomyAside from '../components/TaxonomyAside.astro';
+import { getAllPosts, postUrl as getPostUrl, getSummary } from '../lib/posts';
export async function getStaticPaths() {
- const posts = await getCollection('blog', ({ data }) => !data.draft);
- return posts.map(post => {
- const slug = (post.data.url ?? `/${post.id}/`).replace(/^\/|\/$/g, '');
+ const allPosts = await getAllPosts();
+ return allPosts.map(post => {
+ const slug = getPostUrl(post).replace(/^\/|\/$/g, '');
return { params: { slug }, props: { post } };
});
}
const { title, date, url, categories = [], tags = [] } = post.data;
const postUrl = url ?? `/${post.id}/`;
const year = date.getFullYear().toString();
+const description = getSummary(post.body ?? '');
const canonical = new URL(postUrl, Astro.site).toString();
{ title },
];
-const allPosts = await getCollection('blog', ({ data }) => !data.draft);
+const allPosts = await getAllPosts();
const categoryCounts = new Map<string, number>();
const tagCounts = new Map<string, number>();
for (const p of allPosts) {
const dateDisplay = date.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
---
-<BaseLayout title={title} canonical={canonical}>
+<BaseLayout title={title} description={description} canonical={canonical}>
<Fragment slot="breadcrumb">
<Breadcrumb crumbs={crumbs} />
</Fragment>
---
-import { getCollection } from 'astro:content';
import BaseLayout from '../../../layouts/BaseLayout.astro';
import Breadcrumb from '../../../components/Breadcrumb.astro';
import BlogNav from '../../../components/BlogNav.astro';
+import { getAllPosts, postUrl, wordCount } from '../../../lib/posts';
export async function getStaticPaths() {
- const posts = await getCollection('blog', ({ data }) => !data.draft);
- const years = [...new Set(posts.map(p => p.data.date.getFullYear().toString()))];
+ const allPosts = await getAllPosts();
+ const years = [...new Set(allPosts.map(p => p.data.date.getFullYear().toString()))];
return years.map(year => ({
params: { year },
props: {
- posts: posts
- .filter(p => p.data.date.getFullYear().toString() === year)
- .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()),
+ posts: allPosts.filter(p => p.data.date.getFullYear().toString() === year),
},
}));
}
const currentUrl = `/blog/archive/${year}/`;
-function postUrl(post: any): string {
- return post.data.url ?? `/${post.id}/`;
-}
-
-function wordCount(body: string): number {
- return body.replace(/```[\s\S]*?```/g, '').split(/\s+/).filter(w => w.length > 0).length;
-}
-
const crumbs = [
{ title: 'Home', url: '/' },
{ title: 'Blog', url: '/blog/' },
--- /dev/null
+import rss from '@astrojs/rss';
+import type { APIContext } from 'astro';
+import { getAllPosts, postUrl, getSummary } from '../../../../lib/posts';
+
+export async function getStaticPaths() {
+ const allPosts = await getAllPosts();
+ const years = [...new Set(allPosts.map(p => p.data.date.getFullYear().toString()))];
+ return years.map(year => ({ params: { year } }));
+}
+
+export async function GET(context: APIContext) {
+ const year = context.params.year as string;
+ const allPosts = await getAllPosts();
+ const posts = allPosts.filter(p => p.data.date.getFullYear().toString() === year);
+ return rss({
+ title: `Archive ${year} on juplo`,
+ description: `Posts from ${year} on juplo`,
+ site: context.site!,
+ items: posts.map(post => ({
+ title: post.data.title,
+ link: postUrl(post),
+ pubDate: post.data.date,
+ description: getSummary(post.body ?? ''),
+ author: 'kai@juplo.de (Kai Moritz)',
+ })),
+ customData: [
+ '<language>en-US</language>',
+ '<managingEditor>kai@juplo.de (Kai Moritz)</managingEditor>',
+ '<webMaster>kai@juplo.de (Kai Moritz)</webMaster>',
+ '<copyright>Kai Moritz</copyright>',
+ ].join(''),
+ });
+}
---
-import { getCollection } from 'astro:content';
import BaseLayout from '../../../layouts/BaseLayout.astro';
import Breadcrumb from '../../../components/Breadcrumb.astro';
import BlogNav from '../../../components/BlogNav.astro';
+import { getAllPosts, postUrl, wordCount } from '../../../lib/posts';
const currentUrl = '/blog/archive/';
-const posts = (await getCollection('blog', ({ data }) => !data.draft))
- .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
-
-function postUrl(post: any): string {
- return post.data.url ?? `/${post.id}/`;
-}
-
-function wordCount(body: string): number {
- return body.replace(/```[\s\S]*?```/g, '').split(/\s+/).filter(w => w.length > 0).length;
-}
+const posts = await getAllPosts();
const crumbs = [
{ title: 'Home', url: '/' },
--- /dev/null
+import rss from '@astrojs/rss';
+import type { APIContext } from 'astro';
+import { getAllPosts, postUrl, getSummary } from '../../../lib/posts';
+
+export async function GET(context: APIContext) {
+ const posts = await getAllPosts();
+ return rss({
+ title: 'Archive on juplo',
+ description: 'All posts in the archive on juplo',
+ site: context.site!,
+ items: posts.map(post => ({
+ title: post.data.title,
+ link: postUrl(post),
+ pubDate: post.data.date,
+ description: getSummary(post.body ?? ''),
+ author: 'kai@juplo.de (Kai Moritz)',
+ })),
+ customData: [
+ '<language>en-US</language>',
+ '<managingEditor>kai@juplo.de (Kai Moritz)</managingEditor>',
+ '<webMaster>kai@juplo.de (Kai Moritz)</webMaster>',
+ '<copyright>Kai Moritz</copyright>',
+ ].join(''),
+ });
+}
---
-import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import Breadcrumb from '../../components/Breadcrumb.astro';
import BlogNav from '../../components/BlogNav.astro';
+import { getAllPosts, postUrl, getSummary, wordCount } from '../../lib/posts';
const currentUrl = '/blog/';
-const allPosts = (await getCollection('blog', ({ data }) => !data.draft))
- .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
+const allPosts = await getAllPosts();
const recentPosts = allPosts.slice(0, 3);
.map(([n, c]) => ({ name: n, displayName: capitalize(n), count: c }))
.sort((a, b) => a.displayName.localeCompare(b.displayName));
-function postUrl(post: any): string {
- return post.data.url ?? `/${post.id}/`;
-}
-
-function getSummary(body: string): string {
- let text = body
- .replace(/```[\s\S]*?```/g, '')
- .replace(/`[^`]*`/g, '')
- .replace(/<[^>]+>/g, '')
- .replace(/\{\{[^}]*\}\}/g, '')
- .replace(/!\[[^\]]*\]\([^)]*\)/g, '')
- .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
- .replace(/^#{1,6}\s+/gm, '')
- .replace(/[*_]{1,3}([^*_\n]+)[*_]{1,3}/g, '$1');
- const paras = text.split(/\n{2,}/).map(p => p.trim().replace(/\n/g, ' ')).filter(p => p.length > 20);
- return paras[0]?.substring(0, 350) ?? '';
-}
-
-function wordCount(body: string): number {
- return body.replace(/```[\s\S]*?```/g, '').split(/\s+/).filter(w => w.length > 0).length;
-}
-
const crumbs = [
{ title: 'Home', url: '/' },
{ title: 'Blog' },
--- /dev/null
+import rss from '@astrojs/rss';
+import type { APIContext } from 'astro';
+import { getAllPosts, postUrl, getSummary } from '../../lib/posts';
+
+export async function GET(context: APIContext) {
+ const posts = await getAllPosts();
+ return rss({
+ title: 'Blog on juplo',
+ description: 'Recent content in Blog on juplo',
+ site: context.site!,
+ items: posts.map(post => ({
+ title: post.data.title,
+ link: postUrl(post),
+ pubDate: post.data.date,
+ description: getSummary(post.body ?? ''),
+ author: 'kai@juplo.de (Kai Moritz)',
+ })),
+ customData: [
+ '<language>en-US</language>',
+ '<managingEditor>kai@juplo.de (Kai Moritz)</managingEditor>',
+ '<webMaster>kai@juplo.de (Kai Moritz)</webMaster>',
+ '<copyright>Kai Moritz</copyright>',
+ ].join(''),
+ });
+}
---
-import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import Breadcrumb from '../../components/Breadcrumb.astro';
import BlogNav from '../../components/BlogNav.astro';
+import { getAllPosts, postUrl, wordCount } from '../../lib/posts';
export async function getStaticPaths() {
- const allPosts = await getCollection('blog', ({ data }) => !data.draft);
+ const allPosts = await getAllPosts();
const termMap = new Map<string, typeof allPosts>();
for (const post of allPosts) {
for (const cat of post.data.categories ?? []) {
}
return [...termMap.entries()].map(([term, posts]) => ({
params: { term },
- props: {
- posts: posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()),
- },
+ props: { posts },
}));
}
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
const termDisplay = capitalize(term);
-function postUrl(post: any): string {
- return post.data.url ?? `/${post.id}/`;
-}
-
-function wordCount(body: string): number {
- return body.replace(/```[\s\S]*?```/g, '').split(/\s+/).filter(w => w.length > 0).length;
-}
-
const crumbs = [
{ title: 'Home', url: '/' },
{ title: 'Blog', url: '/blog/' },
--- /dev/null
+import rss from '@astrojs/rss';
+import type { APIContext } from 'astro';
+import { getAllPosts, postUrl, getSummary } from '../../../lib/posts';
+
+export async function getStaticPaths() {
+ const allPosts = await getAllPosts();
+ const termSet = new Set<string>();
+ for (const post of allPosts) {
+ for (const cat of post.data.categories ?? []) termSet.add(cat);
+ }
+ return [...termSet].map(term => ({ params: { term } }));
+}
+
+export async function GET(context: APIContext) {
+ const term = context.params.term as string;
+ const allPosts = await getAllPosts();
+ const posts = allPosts.filter(p => (p.data.categories ?? []).includes(term));
+ const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
+ const termDisplay = capitalize(term);
+ return rss({
+ title: `${termDisplay} on juplo`,
+ description: `Recent content in ${termDisplay} on juplo`,
+ site: context.site!,
+ items: posts.map(post => ({
+ title: post.data.title,
+ link: postUrl(post),
+ pubDate: post.data.date,
+ description: getSummary(post.body ?? ''),
+ author: 'kai@juplo.de (Kai Moritz)',
+ })),
+ customData: [
+ '<language>en-US</language>',
+ '<managingEditor>kai@juplo.de (Kai Moritz)</managingEditor>',
+ '<webMaster>kai@juplo.de (Kai Moritz)</webMaster>',
+ '<copyright>Kai Moritz</copyright>',
+ ].join(''),
+ });
+}
---
-import { getCollection } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
import BlogNav from '../components/BlogNav.astro';
+import { getAllPosts, postUrl } from '../lib/posts';
const currentUrl = '/';
-const recentPosts = (await getCollection('blog', ({ data }) => !data.draft))
- .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
- .slice(0, 5);
-
-function postUrl(post: any): string {
- return post.data.url ?? `/${post.id}/`;
-}
+const recentPosts = (await getAllPosts()).slice(0, 5);
---
<BaseLayout title="juplo">
---
-import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import Breadcrumb from '../../components/Breadcrumb.astro';
import BlogNav from '../../components/BlogNav.astro';
+import { getAllPosts, postUrl, wordCount } from '../../lib/posts';
export async function getStaticPaths() {
- const allPosts = await getCollection('blog', ({ data }) => !data.draft);
+ const allPosts = await getAllPosts();
const termMap = new Map<string, typeof allPosts>();
for (const post of allPosts) {
for (const tag of post.data.tags ?? []) {
}
return [...termMap.entries()].map(([term, posts]) => ({
params: { term },
- props: {
- posts: posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()),
- },
+ props: { posts },
}));
}
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
const termDisplay = capitalize(term);
-function postUrl(post: any): string {
- return post.data.url ?? `/${post.id}/`;
-}
-
-function wordCount(body: string): number {
- return body.replace(/```[\s\S]*?```/g, '').split(/\s+/).filter(w => w.length > 0).length;
-}
-
const crumbs = [
{ title: 'Home', url: '/' },
{ title: 'Blog', url: '/blog/' },
--- /dev/null
+import rss from '@astrojs/rss';
+import type { APIContext } from 'astro';
+import { getAllPosts, postUrl, getSummary } from '../../../lib/posts';
+
+export async function getStaticPaths() {
+ const allPosts = await getAllPosts();
+ const termSet = new Set<string>();
+ for (const post of allPosts) {
+ for (const tag of post.data.tags ?? []) termSet.add(tag);
+ }
+ return [...termSet].map(term => ({ params: { term } }));
+}
+
+export async function GET(context: APIContext) {
+ const term = context.params.term as string;
+ const allPosts = await getAllPosts();
+ const posts = allPosts.filter(p => (p.data.tags ?? []).includes(term));
+ const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
+ const termDisplay = capitalize(term);
+ return rss({
+ title: `${termDisplay} on juplo`,
+ description: `Recent content in ${termDisplay} on juplo`,
+ site: context.site!,
+ items: posts.map(post => ({
+ title: post.data.title,
+ link: postUrl(post),
+ pubDate: post.data.date,
+ description: getSummary(post.body ?? ''),
+ author: 'kai@juplo.de (Kai Moritz)',
+ })),
+ customData: [
+ '<language>en-US</language>',
+ '<managingEditor>kai@juplo.de (Kai Moritz)</managingEditor>',
+ '<webMaster>kai@juplo.de (Kai Moritz)</webMaster>',
+ '<copyright>Kai Moritz</copyright>',
+ ].join(''),
+ });
+}