release: v0.13.0 (#535)
Twenty-nine commits since v0.12.0. See CHANGELOG.md for the full list and the upgrade notes. Ten additions — the SEO package (site URL, robots.txt, sitemap.xml, JSON-LD), slideshow mode, opt-in original downloads, copy-link, previous/next album navigation, alt text from the Immich description, per-album location precision, the update notice, a Help tab, and a subpage cover grid separate from its photo grid. Two fixes: CJK album slugs, and the lightbox control bar. Photo of the Day (#476) was built on `dev` and reverted as not planned; it never appears in a release. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DmsHVRL9yQvckUB1ZpVWre --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: ralksta <ralksta@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
60
lib/structuredData.ts
Normal file
60
lib/structuredData.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* The JSON-LD payload for an album page (#472).
|
||||
*
|
||||
* `ImageGallery` with an `ImageObject` for the cover is the annotation that
|
||||
* pays off for a photography portfolio: it is what a search result can show,
|
||||
* and it is where `creator` and `license` belong.
|
||||
*
|
||||
* Built as a pure function so the shape can be asserted without rendering, and
|
||||
* so the "no site URL, no structured data" rule is stated once.
|
||||
*/
|
||||
|
||||
export interface AlbumStructuredDataInput {
|
||||
siteUrl: string | null;
|
||||
/** Absolute URL of the album page. */
|
||||
pageUrl: string | null;
|
||||
albumName: string;
|
||||
description?: string;
|
||||
/** Absolute URL of the cover image, if there is one. */
|
||||
coverUrl?: string | null;
|
||||
/** The photographer, for `creator`. */
|
||||
creator?: string;
|
||||
/** A licence URL or short identifier, e.g. a Creative Commons link. */
|
||||
license?: string;
|
||||
photoCount?: number;
|
||||
}
|
||||
|
||||
export function albumStructuredData(
|
||||
input: AlbumStructuredDataInput,
|
||||
): Record<string, unknown> | null {
|
||||
// Structured data is a set of claims about URLs. Without an absolute site URL
|
||||
// there is nothing truthful to claim, so nothing is emitted.
|
||||
if (!input.siteUrl || !input.pageUrl) return null;
|
||||
if (!input.albumName.trim()) return null;
|
||||
|
||||
const creator = input.creator?.trim()
|
||||
? { '@type': 'Person', name: input.creator.trim() }
|
||||
: undefined;
|
||||
const license = input.license?.trim() || undefined;
|
||||
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'ImageGallery',
|
||||
name: input.albumName.trim(),
|
||||
url: input.pageUrl,
|
||||
...(input.description?.trim() ? { description: input.description.trim() } : {}),
|
||||
...(typeof input.photoCount === 'number' ? { numberOfItems: input.photoCount } : {}),
|
||||
...(creator ? { creator } : {}),
|
||||
...(license ? { license } : {}),
|
||||
...(input.coverUrl
|
||||
? {
|
||||
image: {
|
||||
'@type': 'ImageObject',
|
||||
contentUrl: input.coverUrl,
|
||||
...(creator ? { creator } : {}),
|
||||
...(license ? { license } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user