feat: blog feed (#221)

This commit is contained in:
Jason Rasmussen
2025-10-07 12:50:11 -04:00
committed by GitHub
parent a07e98da81
commit 70302706de
8 changed files with 95 additions and 5 deletions
+3
View File
@@ -5,6 +5,9 @@
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="currentColor" />
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/blog/feed.xml" />
<link rel="alternate" type="application/atom+xml" title="Atom Feed" href="/blog/atom.xml" />
<link rel="alternate" type="application/json" title="JSON Feed" href="/blog/feed.json" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" class="dark bg-light text-dark">
@@ -0,0 +1,5 @@
import { feed } from '$lib/server';
export const prerender = true;
export const GET = () => new Response(feed.atom1(), { headers: { 'Content-Type': 'application/atom+xml' } });
@@ -0,0 +1,5 @@
import { feed } from '$lib/server';
export const prerender = true;
export const GET = () => new Response(feed.json1(), { headers: { 'Content-Type': 'application/json' } });
@@ -0,0 +1,5 @@
import { feed } from '$lib/server';
export const prerender = true;
export const GET = () => new Response(feed.rss2(), { headers: { 'Content-Type': 'application/rss+xml' } });
+37 -4
View File
@@ -11,12 +11,13 @@
"@mdi/js": "^7.4.47",
"bits-ui": "^2.9.6",
"exifreader": "^4.31.2",
"feed": "^5.1.0",
"luxon": "^3.7.2",
"p-limit": "^7.1.1",
"simple-icons": "^15.9.0"
},
"devDependencies": {
"@immich/ui": "^0.33.0",
"@immich/ui": "^0.34.1",
"@sveltejs/adapter-static": "^3.0.9",
"@sveltejs/kit": "^2.37.1",
"@sveltejs/vite-plugin-svelte": "^6.1.4",
@@ -698,14 +699,15 @@
}
},
"node_modules/@immich/ui": {
"version": "0.33.0",
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.33.0.tgz",
"integrity": "sha512-zLshH4XbHP/wsOkmf7Q8PcrCJVQ2ZhBhhYXFP5vB+xw2lmekdblnJUje/UM/y/9D/HMJ9ptF2iXNKmWy0zX4dw==",
"version": "0.34.1",
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.34.1.tgz",
"integrity": "sha512-40VahJ1JRGsmTYh388Rxb37XXELJNHPkNbc/VkLS1yJKCohfqsoxe8tiTBaIv+uunz4jo6yY6/Airg00Eks91A==",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@mdi/js": "^7.4.47",
"bits-ui": "^2.9.8",
"luxon": "^3.7.2",
"simple-icons": "^15.14.0",
"tailwind-merge": "^3.0.0",
"tailwind-variants": "^3.0.0"
@@ -2815,6 +2817,19 @@
"reusify": "^1.0.4"
}
},
"node_modules/feed": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/feed/-/feed-5.1.0.tgz",
"integrity": "sha512-qGNhgYygnefSkAHHrNHqC7p3R8J0/xQDS/cYUud8er/qD9EFGWyCdUDfULHTJQN1d3H3WprzVwMc9MfB4J50Wg==",
"license": "MIT",
"dependencies": {
"xml-js": "^1.6.11"
},
"engines": {
"node": ">=20",
"pnpm": ">=10"
}
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4035,6 +4050,12 @@
"node": ">=6"
}
},
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
"node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
@@ -4977,6 +4998,18 @@
"node": ">=0.10.0"
}
},
"node_modules/xml-js": {
"version": "1.6.11",
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
"license": "MIT",
"dependencies": {
"sax": "^1.2.4"
},
"bin": {
"xml-js": "bin/cli.js"
}
},
"node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+2 -1
View File
@@ -15,7 +15,7 @@
"format:fix": "prettier --write ."
},
"devDependencies": {
"@immich/ui": "^0.33.0",
"@immich/ui": "^0.34.1",
"@sveltejs/adapter-static": "^3.0.9",
"@sveltejs/kit": "^2.37.1",
"@sveltejs/vite-plugin-svelte": "^6.1.4",
@@ -47,6 +47,7 @@
"@mdi/js": "^7.4.47",
"bits-ui": "^2.9.6",
"exifreader": "^4.31.2",
"feed": "^5.1.0",
"luxon": "^3.7.2",
"p-limit": "^7.1.1",
"simple-icons": "^15.9.0"
+12
View File
@@ -2,9 +2,12 @@ import { DateTime } from 'luxon';
import { PUBLIC_IMMICH_ENV } from '$env/static/public';
export type BlogPost = {
/** uuid-v7, which can be generated with `npx -y uuid v7` */
id: string;
title: string;
description: string;
publishedAt: DateTime;
modifiedAt?: DateTime;
authors: string[];
url: string;
isDraft?: boolean;
@@ -12,6 +15,7 @@ export type BlogPost = {
export const Posts = {
ImmichUi: {
id: '0199bf43-cfec-769c-95a8-2ff1c9774fb0',
title: 'New Svelte component library',
description: 'Learn more about the Svelte component library that is powering this and other Immich websites.',
publishedAt: DateTime.fromObject({ year: 2025, month: 9, day: 25 }),
@@ -19,6 +23,7 @@ export const Posts = {
url: '/blog/immich-ui',
},
NewSyncImplementation: {
id: '0199bf43-a42d-75e8-9cab-06041f89ed14',
title: 'Sync v2',
description: 'Learn how Immich developed a better way to synchronize data with the mobile app.',
publishedAt: DateTime.fromObject({ year: 2025, month: 9, day: 24 }),
@@ -26,6 +31,7 @@ export const Posts = {
url: '/blog/sync-v2',
},
CursedKnowledge: {
id: '0199bf43-7a22-7778-a514-2db731e3c99d',
title: 'Cursed knowledge',
description:
"The story behind the Cursed Knowledge page and how we gained the knowledge we have that we wish we didn't.",
@@ -34,6 +40,7 @@ export const Posts = {
url: '/blog/cursed-knowledge',
},
ImmichApi: {
id: '0199bf43-5985-714c-8e5c-aa94e65a0176',
title: 'New API documentation',
description: 'The Immich API documentation has been moved to a new home.',
publishedAt: DateTime.fromObject({ year: 2025, month: 9, day: 23 }),
@@ -41,6 +48,7 @@ export const Posts = {
url: '/blog/immich-api-documentation',
},
ImmichStore: {
id: '0199bf43-3627-71fe-9747-35d71cb778eb',
title: 'Immich launches merch',
description: 'The Immich merch store is now live! Get your swag today, and learn about our new mascot, Mich.',
publishedAt: DateTime.fromObject({ year: 2025, month: 2, day: 27 }),
@@ -48,6 +56,7 @@ export const Posts = {
url: '/blog/immich-merch',
},
YearInReview2024: {
id: '0199bf43-0a6d-720b-ba24-cb6e8277693e',
title: '2024 - A year in review',
description: 'A review of the major milestones and accomplishments in 2024.',
publishedAt: DateTime.fromObject({ year: 2024, month: 12, day: 30 }),
@@ -55,6 +64,7 @@ export const Posts = {
url: '/blog/2024-year-in-review',
},
PurchaseImmich: {
id: '0199bf42-e615-76ad-8bf5-351584cc59c1',
title: 'Immich introduces product keys',
description: 'Purchase a product key to support Immich development, FUTO, and open source software.',
publishedAt: DateTime.fromObject({ year: 2024, month: 7, day: 18 }),
@@ -62,6 +72,7 @@ export const Posts = {
url: '/blog/immich-product-keys',
},
ImmichJoinsFuto: {
id: '0199bf42-bd9c-7461-86a7-2487f13b6e8b',
title: 'Immich joins FUTO',
description: 'The core team goes full-time on Immich as part of FUTO.',
publishedAt: DateTime.fromObject({ year: 2024, month: 5, day: 1 }),
@@ -69,6 +80,7 @@ export const Posts = {
url: '/blog/immich-joins-futo',
},
YearInReview2023: {
id: '0199bf42-8ee2-70cd-b6ad-02222625932b',
title: '2023 - A year in review',
description: 'A review of the major milestones and accomplishments in 2023.',
publishedAt: DateTime.fromObject({ year: 2023, month: 12, day: 30 }),
+26
View File
@@ -0,0 +1,26 @@
import { blogMetadata } from '$lib';
import { posts } from '$lib/blog';
import { Feed } from 'feed';
import { DateTime } from 'luxon';
export const feed = new Feed({
title: blogMetadata.title,
description: blogMetadata.description,
id: 'https://immich.app/blog',
link: 'https://immich.app/blog',
language: 'en',
copyright: `Copyright © ${DateTime.now().year} FUTO. All rights reserved.`,
favicon: 'https://immich.app/favicon.ico',
});
for (const post of posts) {
feed.addItem({
title: post.title,
id: post.id,
link: 'https://immich.app' + post.url,
description: post.description,
author: post.authors.map((author) => ({ name: author })),
date: (post.modifiedAt ?? post.publishedAt).toJSDate(),
published: post.publishedAt.toJSDate(),
});
}