From 5c3898ec4102d39b0a01cfaac7d5428d3e587551 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 6 May 2026 16:54:30 -0400 Subject: [PATCH] refactor: site metadata --- .prettierignore | 1 + .../src/routes/+layout.svelte | 2 + .../src/routes/+page.svelte | 2 +- .../lib/components/PrivacyPolicyPage.svelte | 3 +- apps/root.immich.app/src/lib/index.ts | 7 --- .../root.immich.app/src/routes/+layout.svelte | 7 +++ apps/root.immich.app/src/routes/+page.svelte | 4 +- .../src/routes/cursed-knowledge/+page.svelte | 4 +- .../src/routes/download/+page.svelte | 3 +- .../src/routes/features/+page.svelte | 3 +- .../src/routes/roadmap/+page.svelte | 3 +- .../src/lib/components/ComponentPage.svelte | 8 ++-- .../src/lib/components/MarkdownPage.svelte | 31 ------------- .../src/lib/components/PageDescription.svelte | 12 ----- apps/ui.immich.app/src/lib/constants.ts | 5 --- .../src/routes/(docs)/components/+page.svelte | 6 +-- .../components/icon-button/+page.svelte | 5 ++- .../(docs)/components/navbar/+page.svelte | 9 ++-- .../(docs)/components/tooltip/+page.svelte | 10 ++--- apps/ui.immich.app/src/routes/+layout.svelte | 7 ++- apps/ui.immich.app/src/routes/+page.svelte | 3 +- apps/ui.immich.app/svelte.config.js | 2 +- eslint.config.ts | 3 +- .../svelte-markdown-preprocess/src/index.ts | 38 ++++++++++++---- .../src/lib/components/Markdown/Page.svelte | 45 +++++++++++++++++++ .../ui/src/lib/components/Markdown/index.ts | 2 + .../lib/components/Navbar/NavbarItem.svelte | 2 +- .../SiteMetadata/SiteMetadata.svelte | 5 ++- packages/ui/src/lib/index.ts | 1 + .../src/lib/services/global-manager.svelte.ts | 19 ++++++++ 30 files changed, 147 insertions(+), 105 deletions(-) delete mode 100644 apps/ui.immich.app/src/lib/components/MarkdownPage.svelte delete mode 100644 apps/ui.immich.app/src/lib/components/PageDescription.svelte create mode 100644 packages/ui/src/lib/components/Markdown/Page.svelte create mode 100644 packages/ui/src/lib/services/global-manager.svelte.ts diff --git a/.prettierignore b/.prettierignore index d1e76fe7..17e3e880 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ pnpm-lock.yaml yarn.lock src/lib/assets dist/ +deployment/ CHANGELOG.md .svelte-kit/ .release-please-manifest.json diff --git a/apps/awesome.immich.app/src/routes/+layout.svelte b/apps/awesome.immich.app/src/routes/+layout.svelte index 349e2d4d..41e89a99 100644 --- a/apps/awesome.immich.app/src/routes/+layout.svelte +++ b/apps/awesome.immich.app/src/routes/+layout.svelte @@ -13,6 +13,7 @@ ControlBarHeader, ControlBarOverflow, getSiteProviders, + globalManager, Logo, ThemeSwitcher, TooltipProvider, @@ -26,6 +27,7 @@ const { children }: Props = $props(); + globalManager.setSiteMetadata(siteMetadata); commandPaletteManager.enable(); diff --git a/apps/awesome.immich.app/src/routes/+page.svelte b/apps/awesome.immich.app/src/routes/+page.svelte index 1b794336..cb70b9c8 100644 --- a/apps/awesome.immich.app/src/routes/+page.svelte +++ b/apps/awesome.immich.app/src/routes/+page.svelte @@ -27,7 +27,7 @@ let filteredCategories = $derived(filterProjects(query)); - + {siteMetadata.title} {siteMetadata.description} diff --git a/apps/root.immich.app/src/lib/components/PrivacyPolicyPage.svelte b/apps/root.immich.app/src/lib/components/PrivacyPolicyPage.svelte index 5e0d36ae..36855dde 100644 --- a/apps/root.immich.app/src/lib/components/PrivacyPolicyPage.svelte +++ b/apps/root.immich.app/src/lib/components/PrivacyPolicyPage.svelte @@ -1,5 +1,4 @@ - +
diff --git a/apps/root.immich.app/src/lib/index.ts b/apps/root.immich.app/src/lib/index.ts index 5e78eafb..84035ba1 100644 --- a/apps/root.immich.app/src/lib/index.ts +++ b/apps/root.immich.app/src/lib/index.ts @@ -4,13 +4,6 @@ import { defaultProvider, type ActionItem } from '@immich/ui'; import fm from 'front-matter'; import { DateTime } from 'luxon'; -export const siteMetadata = { - title: 'Immich', - description: - 'Self-hosted photo and video management solution. Easily back up, organize, and manage your photos on your own server. Immich helps you browse, search and organize your photos and videos with ease, without sacrificing your privacy.', - imageUrl: '/img/social-preview.png', -}; - export const blogMetadata = { title: 'Immich Blog', description: 'Latest updates, announcements, and stories from the Immich team.', diff --git a/apps/root.immich.app/src/routes/+layout.svelte b/apps/root.immich.app/src/routes/+layout.svelte index ba1852a5..b17a7c6f 100644 --- a/apps/root.immich.app/src/routes/+layout.svelte +++ b/apps/root.immich.app/src/routes/+layout.svelte @@ -15,6 +15,7 @@ CommandPaletteProvider, Constants, getSiteProviders, + globalManager, IconButton, Link, Logo, @@ -77,6 +78,12 @@ return active ? 'primary' : 'secondary'; }; + globalManager.setSiteMetadata({ + title: 'Immich', + description: + 'Self-hosted photo and video management solution. Easily back up, organize, and manage your photos on your own server. Immich helps you browse, search and organize your photos and videos with ease, without sacrificing your privacy.', + imageUrl: '/img/social-preview.png', + }); commandPaletteManager.enable(); diff --git a/apps/root.immich.app/src/routes/+page.svelte b/apps/root.immich.app/src/routes/+page.svelte index 7aa298ae..cb56f84b 100644 --- a/apps/root.immich.app/src/routes/+page.svelte +++ b/apps/root.immich.app/src/routes/+page.svelte @@ -4,7 +4,6 @@ import qrCodeLight from '$common/img/app-qr-code-light.svg'; import screenshotDark from '$common/img/screenshot-dark.webp'; import screenshotLight from '$common/img/screenshot-light.webp'; - import { siteMetadata } from '$lib'; import { appStoreBadge, Button, @@ -30,8 +29,7 @@ ]; - - + {#if browser} diff --git a/apps/root.immich.app/src/routes/cursed-knowledge/+page.svelte b/apps/root.immich.app/src/routes/cursed-knowledge/+page.svelte index 5f52f050..5f55e161 100644 --- a/apps/root.immich.app/src/routes/cursed-knowledge/+page.svelte +++ b/apps/root.immich.app/src/routes/cursed-knowledge/+page.svelte @@ -1,6 +1,6 @@ - + {pageMetadata.title} diff --git a/apps/root.immich.app/src/routes/download/+page.svelte b/apps/root.immich.app/src/routes/download/+page.svelte index d7cebf20..c2c729a4 100644 --- a/apps/root.immich.app/src/routes/download/+page.svelte +++ b/apps/root.immich.app/src/routes/download/+page.svelte @@ -2,7 +2,6 @@ import portainerIcon from '$common/img/portainer.svg'; import truenasIcon from '$common/img/truenas-scale.svg'; import unraidIcon from '$common/img/unraid.svg'; - import { siteMetadata } from '$lib'; import { appStoreBadge, Button, @@ -30,7 +29,7 @@ }; - + {pageMetadata.title} diff --git a/apps/root.immich.app/src/routes/features/+page.svelte b/apps/root.immich.app/src/routes/features/+page.svelte index a2145310..8011e5e7 100644 --- a/apps/root.immich.app/src/routes/features/+page.svelte +++ b/apps/root.immich.app/src/routes/features/+page.svelte @@ -1,5 +1,4 @@ - + diff --git a/apps/root.immich.app/src/routes/roadmap/+page.svelte b/apps/root.immich.app/src/routes/roadmap/+page.svelte index a157de49..149f7cae 100644 --- a/apps/root.immich.app/src/routes/roadmap/+page.svelte +++ b/apps/root.immich.app/src/routes/roadmap/+page.svelte @@ -1,6 +1,5 @@ - + Roadmap diff --git a/apps/ui.immich.app/src/lib/components/ComponentPage.svelte b/apps/ui.immich.app/src/lib/components/ComponentPage.svelte index 9f01d99e..d9593e5b 100644 --- a/apps/ui.immich.app/src/lib/components/ComponentPage.svelte +++ b/apps/ui.immich.app/src/lib/components/ComponentPage.svelte @@ -1,16 +1,16 @@ - + diff --git a/apps/ui.immich.app/src/lib/components/MarkdownPage.svelte b/apps/ui.immich.app/src/lib/components/MarkdownPage.svelte deleted file mode 100644 index 7b17538e..00000000 --- a/apps/ui.immich.app/src/lib/components/MarkdownPage.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - - - - -
- {page.title} - {#if page.description} - {page.description} - {/if} -
- - {@render children?.()} -
diff --git a/apps/ui.immich.app/src/lib/components/PageDescription.svelte b/apps/ui.immich.app/src/lib/components/PageDescription.svelte deleted file mode 100644 index f09014c3..00000000 --- a/apps/ui.immich.app/src/lib/components/PageDescription.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/apps/ui.immich.app/src/lib/constants.ts b/apps/ui.immich.app/src/lib/constants.ts index 78471226..9f196bc1 100644 --- a/apps/ui.immich.app/src/lib/constants.ts +++ b/apps/ui.immich.app/src/lib/constants.ts @@ -106,11 +106,6 @@ export type ExampleItem = { export type ExampleCardProps = ExampleItem; -export const siteMetadata = { - title: 'Immich UI', - description: 'A Svelte component library for Immich', -}; - export type ComponentItem = { name: string; title?: string; diff --git a/apps/ui.immich.app/src/routes/(docs)/components/+page.svelte b/apps/ui.immich.app/src/routes/(docs)/components/+page.svelte index 5b96ac84..79d37755 100644 --- a/apps/ui.immich.app/src/routes/(docs)/components/+page.svelte +++ b/apps/ui.immich.app/src/routes/(docs)/components/+page.svelte @@ -1,14 +1,14 @@ - + {#each components as component (component.name)} {/each} - +
diff --git a/apps/ui.immich.app/src/routes/(docs)/components/icon-button/+page.svelte b/apps/ui.immich.app/src/routes/(docs)/components/icon-button/+page.svelte index b553fc88..960fb9cd 100644 --- a/apps/ui.immich.app/src/routes/(docs)/components/icon-button/+page.svelte +++ b/apps/ui.immich.app/src/routes/(docs)/components/icon-button/+page.svelte @@ -2,7 +2,6 @@ import ComponentExamples from '$lib/components/ComponentExamples.svelte'; import ComponentLink from '$lib/components/ComponentLink.svelte'; import ComponentPage from '$lib/components/ComponentPage.svelte'; - import PageDescription from '$lib/components/PageDescription.svelte'; import BasicExample from './BasicExample.svelte'; import basicExample from './BasicExample.svelte?raw'; import ColorExample from './ColorExample.svelte'; @@ -22,7 +21,9 @@ - An icon-only version of the component + {#snippet descriptionBody()} + An icon-only version of the component + {/snippet} import ComponentExamples from '$lib/components/ComponentExamples.svelte'; import ComponentPage from '$lib/components/ComponentPage.svelte'; - import PageDescription from '$lib/components/PageDescription.svelte'; import { asComponentHref } from '$lib/utilities.js'; - import { Link } from '@immich/ui'; + import { Link, Text } from '@immich/ui'; import BasicExample from './BasicExample.svelte'; import basicExample from './BasicExample.svelte?raw'; import ExpandExample from './ExpandExample.svelte'; @@ -15,10 +14,10 @@ - This component exports a NavbarItem for creating navigation menus. This is usually used in conjunction with the + This component exports a NavbarItem for creating navigation menus. This is usually used in conjunction with the AppSidebar component component import ComponentExamples from '$lib/components/ComponentExamples.svelte'; import ComponentPage from '$lib/components/ComponentPage.svelte'; - import PageDescription from '$lib/components/PageDescription.svelte'; import BasicExample from './BasicExample.svelte'; import basicExample from './BasicExample.svelte?raw'; - - - Displays supplementary information when users hover over or interact with an element. - - + diff --git a/apps/ui.immich.app/src/routes/+layout.svelte b/apps/ui.immich.app/src/routes/+layout.svelte index fb4ef746..abc0454f 100644 --- a/apps/ui.immich.app/src/routes/+layout.svelte +++ b/apps/ui.immich.app/src/routes/+layout.svelte @@ -1,20 +1,25 @@ - + diff --git a/apps/ui.immich.app/svelte.config.js b/apps/ui.immich.app/svelte.config.js index c10b28ca..8d20daa8 100644 --- a/apps/ui.immich.app/svelte.config.js +++ b/apps/ui.immich.app/svelte.config.js @@ -11,7 +11,7 @@ const config = { preprocess: [ svelteMarkdownPreprocess({ layouts: { - default: '$lib/components/MarkdownPage.svelte', + default: { name: 'Markdown.Page', source: '@immich/ui', import: false }, }, }), vitePreprocess(), diff --git a/eslint.config.ts b/eslint.config.ts index 326c8b49..b7302312 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -72,10 +72,11 @@ export default defineConfig([ 'apps/**/backend/dist', 'apps/**/.wrangler', 'apps/**/backend/worker-configuration.d.ts', + 'common/', + 'deployment/**', 'packages/**/build/', 'packages/**/.svelte-kit/', 'packages/**/dist/', - 'common/', ], }, ]); diff --git a/packages/svelte-markdown-preprocess/src/index.ts b/packages/svelte-markdown-preprocess/src/index.ts index ef9f3a3a..e2cd3acc 100644 --- a/packages/svelte-markdown-preprocess/src/index.ts +++ b/packages/svelte-markdown-preprocess/src/index.ts @@ -9,7 +9,7 @@ export type FileWithContent = { filename: string; content: string }; export type FileWithFrontMatter = { filename: string; attributes: FrontMatterAttributes; body: string }; export type FileWithScriptBody = FileWithFrontMatter & { scriptBody: string }; export type FileWithMarkup = FileWithScriptBody & { markup: string }; -export type FileWithLayout = FileWithMarkup & { layout?: string }; +export type FileWithLayout = FileWithMarkup & { layout?: SvelteMarkdownImport }; export type FileWithSvelte = FileWithLayout & { svelte: string }; export type FrontMatterAttributes = { @@ -17,10 +17,14 @@ export type FrontMatterAttributes = { [key: string]: unknown; }; +export type SvelteMarkdownLayoutHandler = (file: FileWithMarkup) => SvelteMarkdownImportLike; +export type SvelteMarkdownImportLike = SvelteMarkdownImport | string; +export type SvelteMarkdownImport = { name: string; import?: string | false; source: string }; + export type SvelteMarkdownPreprocessLayouts = { - _?: string; - default?: string; - [key: string]: string | undefined; + _?: SvelteMarkdownImportLike | SvelteMarkdownLayoutHandler; + default?: SvelteMarkdownImportLike | SvelteMarkdownLayoutHandler; + [key: string]: SvelteMarkdownImportLike | SvelteMarkdownLayoutHandler | undefined; }; const SCRIPT_BODY_REGEX = /(?(.|\n)*?)<\/script>/; @@ -97,7 +101,21 @@ export class SvelteMarkdownPreprocess { parseLayout(file: FileWithMarkup): MaybePromise { const layoutKey = file.attributes.layout; - const layout = layoutKey ? this.#layouts[layoutKey] : (this.#layouts.default ?? this.#layouts._); + + let layout = layoutKey ? this.#layouts[layoutKey] : (this.#layouts.default ?? this.#layouts._); + + if (layoutKey && !layout) { + throw new Error(`File references unknown layout key: ${file.filename} => ${layoutKey} => ?`); + } + + if (typeof layout === 'function') { + layout = layout(file); + } + + if (typeof layout === 'string') { + layout = { name: 'Layout', source: layout }; + } + return { ...file, layout }; } @@ -108,10 +126,12 @@ export class SvelteMarkdownPreprocess { return { ...file, svelte }; } - scriptImports(file: FileWithLayout): Array { + scriptImports({ layout }: FileWithLayout): Array { return [ ` import { Markdown } from '@immich/ui';`, - file.layout ? ` import Layout from '${file.layout}';` : undefined, + layout && layout.import !== false + ? ` import ${layout.import ?? layout.name} from '${layout.source}';` + : undefined, ]; } @@ -132,7 +152,9 @@ export class SvelteMarkdownPreprocess { } createSvelteTemplate(file: FileWithLayout): string { - return (file.layout ? [``, file.markup, ''] : [file.markup]).join('\n'); + return ( + file.layout ? [`<${file.layout.name} {attributes}>`, file.markup, ``] : [file.markup] + ).join('\n'); } } diff --git a/packages/ui/src/lib/components/Markdown/Page.svelte b/packages/ui/src/lib/components/Markdown/Page.svelte new file mode 100644 index 00000000..05dd9c2b --- /dev/null +++ b/packages/ui/src/lib/components/Markdown/Page.svelte @@ -0,0 +1,45 @@ + + + + + +
+ + {#if title} + {@render title()} + {:else} + {page.title} + {/if} + + {#if page.description || description} + + {#if typeof description === 'function'} + {@render description()} + {:else} + {page.description} + {/if} + + {/if} +
+ + {@render children?.()} +
diff --git a/packages/ui/src/lib/components/Markdown/index.ts b/packages/ui/src/lib/components/Markdown/index.ts index 8046c82c..f5ea497e 100644 --- a/packages/ui/src/lib/components/Markdown/index.ts +++ b/packages/ui/src/lib/components/Markdown/index.ts @@ -7,6 +7,7 @@ import LineBreak from './LineBreak.svelte'; import Link from './Link.svelte'; import List from './List.svelte'; import ListItem from './ListItem.svelte'; +import Page from './Page.svelte'; import Paragraph from './Paragraph.svelte'; import Space from './Space.svelte'; import Table from './Table.svelte'; @@ -27,6 +28,7 @@ export const Markdown = { Image, ListItem, List, + Page, Paragraph, Table, TableHeader, diff --git a/packages/ui/src/lib/components/Navbar/NavbarItem.svelte b/packages/ui/src/lib/components/Navbar/NavbarItem.svelte index 57509cc6..b482a462 100644 --- a/packages/ui/src/lib/components/Navbar/NavbarItem.svelte +++ b/packages/ui/src/lib/components/Navbar/NavbarItem.svelte @@ -63,7 +63,7 @@