release: v0.12.0 (#520)
Forty-one commits since v0.11.0. See CHANGELOG.md for the full list and the upgrade notes.
This commit is contained in:
@@ -31,7 +31,12 @@ import {
|
||||
isAlbumSortMode,
|
||||
type AlbumSortMode,
|
||||
} from '@/lib/albumSort';
|
||||
import type { AlbumEntryObject } from '@/lib/config/schema';
|
||||
import {
|
||||
COVER_GRID_COLUMNS_MAX,
|
||||
COVER_GRID_COLUMNS_MIN,
|
||||
COVER_GRID_GAP_MAX,
|
||||
type AlbumEntryObject,
|
||||
} from '@/lib/config/schema';
|
||||
import {
|
||||
IconArrowDown,
|
||||
IconArrowUp,
|
||||
@@ -122,6 +127,21 @@ interface Subpage {
|
||||
grid?: { columns?: number; gap?: number; aspectRatio?: string; layout?: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops keys the editor cleared so an emptied field never persists as `null`
|
||||
* in gallery.yaml, and returns undefined once nothing is left — `handleSave`
|
||||
* only writes `grid` when it is truthy.
|
||||
*/
|
||||
function normalizeSubpageGrid(grid: Subpage['grid']): Subpage['grid'] {
|
||||
if (!grid) return undefined;
|
||||
const next = { ...grid };
|
||||
for (const key of Object.keys(next) as Array<keyof typeof next>) {
|
||||
const value = next[key];
|
||||
if (value === undefined || value === '') delete next[key];
|
||||
}
|
||||
return Object.keys(next).length > 0 ? next : undefined;
|
||||
}
|
||||
|
||||
interface GalleryState {
|
||||
hero: string[];
|
||||
albums: AlbumEntry[];
|
||||
@@ -1452,6 +1472,80 @@ export default function PageBuilder() {
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Album cover grid — hidden in essay mode, which
|
||||
renders a story instead of a cover grid. The
|
||||
album count is deliberately not checked: it
|
||||
changes in this very drawer, and a field that
|
||||
vanishes while you edit is worse than one that
|
||||
has no effect yet. */}
|
||||
{sp.grid?.layout !== 'essay' && sp.essayText == null && (
|
||||
<div className="admin-field" style={{ marginTop: '1rem' }}>
|
||||
<label>Album Cover Grid</label>
|
||||
<div style={{ display: 'flex', gap: '12px' }}>
|
||||
<input
|
||||
type="number"
|
||||
min={COVER_GRID_COLUMNS_MIN}
|
||||
max={COVER_GRID_COLUMNS_MAX}
|
||||
value={sp.grid?.columns ?? ''}
|
||||
placeholder="Columns (site default)"
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value;
|
||||
const columns = raw === '' ? undefined : Number(raw);
|
||||
updateSubpage(spIndex, {
|
||||
grid: normalizeSubpageGrid({
|
||||
...(sp.grid || {}),
|
||||
columns:
|
||||
columns != null &&
|
||||
columns >= COVER_GRID_COLUMNS_MIN &&
|
||||
columns <= COVER_GRID_COLUMNS_MAX
|
||||
? columns
|
||||
: undefined,
|
||||
}),
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
padding: '8px 12px',
|
||||
borderRadius: '6px',
|
||||
flex: 1,
|
||||
fontSize: '0.9rem',
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
max={COVER_GRID_GAP_MAX}
|
||||
value={sp.grid?.gap ?? ''}
|
||||
placeholder="Gap in px (theme default)"
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value;
|
||||
const gap = raw === '' ? undefined : Number(raw);
|
||||
updateSubpage(spIndex, {
|
||||
grid: normalizeSubpageGrid({
|
||||
...(sp.grid || {}),
|
||||
gap:
|
||||
gap != null && gap >= 0 && gap <= COVER_GRID_GAP_MAX
|
||||
? gap
|
||||
: undefined,
|
||||
}),
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
padding: '8px 12px',
|
||||
borderRadius: '6px',
|
||||
flex: 1,
|
||||
fontSize: '0.9rem',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="admin-field-hint">
|
||||
Overrides how the album covers are tiled on this page. Leave the
|
||||
column count empty to follow the site-wide grid setting, and the
|
||||
gap empty to keep the theme's own spacing. Tablet widths
|
||||
show at most two covers per row.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Visual Photo Essay Builder (if layout === 'essay') */}
|
||||
|
||||
Reference in New Issue
Block a user