release: v0.20.0
This commit is contained in:
@@ -4,18 +4,28 @@ import { useState } from 'react';
|
||||
import * as Icons from '../Icons';
|
||||
import type { SaveStatus } from '../SaveBar';
|
||||
import { resolveExifDisplay } from '@/lib/config/schema';
|
||||
import { SUPPORTED_LOCALES } from '@/lib/i18n';
|
||||
import { SUPPORTED_LOCALES, getDictionary, resolveLocale } from '@/lib/i18n';
|
||||
import { FeatureGroup, FeatureRow, SettingRow } from './fields';
|
||||
import type { SectionProps } from './types';
|
||||
import { useConfirm } from '../ConfirmDialog';
|
||||
|
||||
export default function GeneralSection({ settings, update, updateMany }: SectionProps) {
|
||||
export default function GeneralSection({
|
||||
settings,
|
||||
update,
|
||||
updateMany,
|
||||
siteTitleEnv,
|
||||
}: SectionProps & { siteTitleEnv?: string | null }) {
|
||||
const confirm = useConfirm();
|
||||
// What the site shows while the field is empty, resolved as getConfig does:
|
||||
// SITE_TITLE, else the site language's word for a gallery (QA A-15).
|
||||
const fallbackTitle = siteTitleEnv || getDictionary(resolveLocale(settings.lang)).common.gallery;
|
||||
// Collapsed by default: the four metadata switches are a detail of one
|
||||
// decision, and showing them permanently is what made the section a wall (#510).
|
||||
const [metadataOpen, setMetadataOpen] = useState(false);
|
||||
const [faviconUploading, setFaviconUploading] = useState(false);
|
||||
const [faviconStatus, setFaviconStatus] = useState<SaveStatus>(null);
|
||||
/** Bumped after an upload or reset so the preview fetches the new icon. */
|
||||
const [faviconVersion, setFaviconVersion] = useState(0);
|
||||
|
||||
// Resolved the same way the site resolves it, so the switches show what a
|
||||
// visitor actually sees — including a config that only ever set the older
|
||||
@@ -62,8 +72,14 @@ export default function GeneralSection({ settings, update, updateMany }: Section
|
||||
id="general-site-title"
|
||||
value={settings.title || ''}
|
||||
onChange={(e) => update('title', e.target.value)}
|
||||
placeholder="My Portfolio"
|
||||
placeholder={fallbackTitle}
|
||||
aria-describedby="general-site-title-hint"
|
||||
/>
|
||||
<p id="general-site-title-hint" className="admin-field-hint">
|
||||
{settings.title
|
||||
? 'Shown in the header, the browser tab and link previews.'
|
||||
: `Empty, so the site shows “${fallbackTitle}”${siteTitleEnv ? ', from SITE_TITLE' : ''}.`}
|
||||
</p>
|
||||
</div>
|
||||
<div className="admin-field">
|
||||
<label htmlFor="general-subtitle">Subtitle</label>
|
||||
@@ -136,6 +152,13 @@ export default function GeneralSection({ settings, update, updateMany }: Section
|
||||
checked={settings.proofing?.enabled !== false}
|
||||
onToggle={() => update('proofing.enabled', settings.proofing?.enabled === false)}
|
||||
/>
|
||||
<FeatureRow
|
||||
icon={<Icons.IconSearch size={15} />}
|
||||
title="Photo zoom"
|
||||
description="Visitors zoom into a photo to full resolution (1:1) to check sharpness. Pages and albums can override this"
|
||||
checked={settings.zoom === true}
|
||||
onToggle={() => update('zoom', !settings.zoom)}
|
||||
/>
|
||||
</div>
|
||||
{/* Shown even while proofing is off globally: a subpage can switch it
|
||||
on for itself (`proofing: true`), so the address still has to be
|
||||
@@ -249,12 +272,30 @@ export default function GeneralSection({ settings, update, updateMany }: Section
|
||||
</FeatureGroup>
|
||||
|
||||
<div className="admin-field favicon-field">
|
||||
<span className="admin-field-label">Favicon</span>
|
||||
<span className="admin-field-label" id="favicon-label">
|
||||
Favicon
|
||||
</span>
|
||||
<div className="favicon-row">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element -- the site's own icon route, a 32px preview */}
|
||||
<img
|
||||
className="favicon-preview"
|
||||
src={`/api/favicon?v=${faviconVersion}`}
|
||||
alt="Current favicon"
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
{/* The native file input stays the control — focusable, keyboard
|
||||
operable, announced as a file picker — but is visually hidden;
|
||||
its label is drawn as the admin's secondary button, with the
|
||||
focus ring passed through (QA A-18). */}
|
||||
<input
|
||||
id="favicon-file"
|
||||
className="favicon-file-input"
|
||||
type="file"
|
||||
accept=".svg,.png,.ico,.jpg,.jpeg"
|
||||
disabled={faviconUploading}
|
||||
aria-labelledby="favicon-label favicon-file-button"
|
||||
aria-describedby="favicon-hint"
|
||||
onChange={async (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
@@ -273,6 +314,7 @@ export default function GeneralSection({ settings, update, updateMany }: Section
|
||||
? { kind: 'success', message: data.message }
|
||||
: { kind: 'error', message: `Error: ${data.error}` },
|
||||
);
|
||||
if (res.ok) setFaviconVersion((v) => v + 1);
|
||||
} catch {
|
||||
setFaviconStatus({ kind: 'error', message: 'Error: Upload failed' });
|
||||
} finally {
|
||||
@@ -281,9 +323,16 @@ export default function GeneralSection({ settings, update, updateMany }: Section
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
htmlFor="favicon-file"
|
||||
id="favicon-file-button"
|
||||
className="admin-btn admin-btn-sm admin-btn-secondary favicon-upload-btn"
|
||||
>
|
||||
<Icons.IconImage size={14} /> Upload icon
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
className="admin-btn"
|
||||
className="admin-btn admin-btn-sm admin-btn-secondary"
|
||||
disabled={faviconUploading}
|
||||
onClick={async () => {
|
||||
// Deletes the uploaded file at once, outside the staged form.
|
||||
@@ -304,6 +353,7 @@ export default function GeneralSection({ settings, update, updateMany }: Section
|
||||
? { kind: 'success', message: data.message }
|
||||
: { kind: 'error', message: `Error: ${data.error}` },
|
||||
);
|
||||
if (res.ok) setFaviconVersion((v) => v + 1);
|
||||
} catch {
|
||||
setFaviconStatus({ kind: 'error', message: 'Error: Reset failed' });
|
||||
} finally {
|
||||
@@ -318,7 +368,7 @@ export default function GeneralSection({ settings, update, updateMany }: Section
|
||||
{faviconStatus && (
|
||||
<p className={`save-message ${faviconStatus.kind}`}>{faviconStatus.message}</p>
|
||||
)}
|
||||
<span className="admin-field-hint">
|
||||
<span id="favicon-hint" className="admin-field-hint">
|
||||
SVG, PNG, ICO, or JPEG — max 512 kB. Stored in the content volume. Reset restores the
|
||||
bundled default.
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user