release: v0.16.0
See CHANGELOG.md for the full v0.16.0 entry. --------- 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> Co-authored-by: Ralf <rohlenbo79@gmail.com> Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com> Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
This commit is contained in:
@@ -10,8 +10,13 @@ import { ALBUM_SORT_MODES, isAlbumSortMode, type AlbumSortMode } from '../albumS
|
||||
import { LOCATION_PRECISIONS, isLocationPrecision, type LocationPrecision } from '../mapPrecision';
|
||||
import {
|
||||
slugify,
|
||||
findAlbumSlugCollisions,
|
||||
resolveExifDisplay,
|
||||
resolveColorMode,
|
||||
clamp,
|
||||
PHOTO_GRID_COLUMNS_MIN,
|
||||
PHOTO_GRID_COLUMNS_MAX,
|
||||
PHOTO_GRID_GAP_MAX,
|
||||
AppConfig,
|
||||
AlbumEntryObject,
|
||||
SubpageConfig,
|
||||
@@ -65,11 +70,22 @@ interface RawGridOverrides {
|
||||
layout?: string;
|
||||
}
|
||||
|
||||
/** Converts raw YAML grid overrides into a typed partial GridConfig. */
|
||||
/**
|
||||
* Converts raw YAML grid overrides into a typed partial GridConfig.
|
||||
*
|
||||
* `columns` and `gap` are clamped here, not left for the renderer: a subpage
|
||||
* or album override reaches `--grid-columns` unclamped (app/[...path]/page.tsx),
|
||||
* so `columns: -1` produced an invalid `repeat(-1, 1fr)` and collapsed the
|
||||
* whole grid to one column with no error anywhere (#633). The cover grid had
|
||||
* this already (buildCoverGridVars); this is the same treatment for the photo
|
||||
* grid it was missing from.
|
||||
*/
|
||||
function parseGridOverrides(raw: RawGridOverrides): Partial<GridConfig> {
|
||||
return {
|
||||
...(raw.columns != null ? { columns: raw.columns } : {}),
|
||||
...(raw.gap != null ? { gap: raw.gap } : {}),
|
||||
...(raw.columns != null
|
||||
? { columns: clamp(raw.columns, PHOTO_GRID_COLUMNS_MIN, PHOTO_GRID_COLUMNS_MAX) }
|
||||
: {}),
|
||||
...(raw.gap != null ? { gap: clamp(raw.gap, 0, PHOTO_GRID_GAP_MAX) } : {}),
|
||||
...(raw.aspectRatio != null ? { aspectRatio: raw.aspectRatio } : {}),
|
||||
...(raw.layout != null
|
||||
? {
|
||||
@@ -418,10 +434,53 @@ export function deriveGallery(gallery: GalleryYaml): GalleryDerivation {
|
||||
|
||||
const subpageAlbumIds = new Set(subpages.flatMap((sp) => sp.albumIds));
|
||||
const allAlbumIds = [...new Set([...standaloneAlbumIds, ...subpageAlbumIds])];
|
||||
const standaloneAlbums = standaloneAlbumIds.filter((id) => !subpageAlbumIds.has(id));
|
||||
|
||||
// Every consumer that resolves a slug takes the first match — getAlbumBySlug,
|
||||
// the sitemap, findPassword — so two subpages that slugify alike leave the
|
||||
// second unreachable and its password / albums resolved from the first
|
||||
// (#632). slugify() folds diacritics and punctuation, so distinct names
|
||||
// collide easily: "Portfolio 2024" and "Portfolio-2024" both become
|
||||
// "portfolio-2024".
|
||||
const subpagesBySlug = new Map<string, string[]>();
|
||||
for (const sp of subpages) {
|
||||
const names = subpagesBySlug.get(sp.slug) ?? [];
|
||||
names.push(sp.name);
|
||||
subpagesBySlug.set(sp.slug, names);
|
||||
}
|
||||
for (const [slug, names] of subpagesBySlug) {
|
||||
if (names.length > 1) {
|
||||
throw new Error(
|
||||
`Subpages ${names.map((n) => `"${n}"`).join(' and ')} all produce the slug "${slug}". ` +
|
||||
`Rename one so their URLs do not collide.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Same rule applies to albums that share a route (standalone albums among
|
||||
// themselves, or one subpage's albums including its sections). Only a title
|
||||
// override is known here without asking Immich — an album that keeps its
|
||||
// Immich name can still collide, which is what the doctor's equivalent
|
||||
// check, run against the live album list, is for.
|
||||
const checkAlbumOverrideCollisions = (context: string, ids: string[]) => {
|
||||
const named = ids
|
||||
.map((id) => ({ id, name: albumOverrides[id] }))
|
||||
.filter((a): a is { id: string; name: string } => !!a.name);
|
||||
for (const { slug, albums } of findAlbumSlugCollisions(named)) {
|
||||
throw new Error(
|
||||
`${context}: albums titled ${albums.map((a) => `"${a.name}"`).join(' and ')} all produce ` +
|
||||
`the slug "${slug}". Give one a different title override.`,
|
||||
);
|
||||
}
|
||||
};
|
||||
checkAlbumOverrideCollisions('gallery.yaml albums', standaloneAlbums);
|
||||
for (const sp of subpages) {
|
||||
checkAlbumOverrideCollisions(`subpage "${sp.name}"`, sp.albumIds);
|
||||
}
|
||||
|
||||
return {
|
||||
albums: allAlbumIds,
|
||||
standaloneAlbums: standaloneAlbumIds.filter((id) => !subpageAlbumIds.has(id)),
|
||||
standaloneAlbums,
|
||||
subpages,
|
||||
albumOverrides,
|
||||
albumDescriptions,
|
||||
@@ -587,8 +646,11 @@ export function getConfig(): AppConfig {
|
||||
exifOnHover: settings.exifOnHover !== false,
|
||||
exif: resolveExifDisplay(settings.exif, settings.exifOnHover),
|
||||
grid: {
|
||||
columns: settings.grid?.columns ?? 3,
|
||||
gap: settings.grid?.gap ?? 12,
|
||||
columns:
|
||||
settings.grid?.columns != null
|
||||
? clamp(settings.grid.columns, PHOTO_GRID_COLUMNS_MIN, PHOTO_GRID_COLUMNS_MAX)
|
||||
: 3,
|
||||
gap: settings.grid?.gap != null ? clamp(settings.grid.gap, 0, PHOTO_GRID_GAP_MAX) : 12,
|
||||
aspectRatio: settings.grid?.aspectRatio ?? '1',
|
||||
layout: (VALID_LAYOUTS.includes(settings.grid?.layout ?? '')
|
||||
? settings.grid!.layout
|
||||
|
||||
Reference in New Issue
Block a user