release: v0.19.0

Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of
[CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md)
for the full list.

Highlights: client proofing links (#683), custom content pages (#738),
asset picker search (#740), concurrent-edit detection (#742), env-locked
settings (#739), an accessibility baseline (#741), three security fixes
with advisories (#737, #755, #759), and a pre-release quality pass
(#780–#797).

---------

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>
Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
This commit is contained in:
Ralksta
2026-09-29 22:50:30 +02:00
committed by GitHub
parent 5f251923f0
commit ad8b09700b
297 changed files with 19398 additions and 1800 deletions

View File

@@ -29,6 +29,7 @@ import {
IconX,
} from './Icons';
import { BlockBadge } from './BlockBadge';
import { InlineMarkdownField } from './InlineMarkdownField';
interface EssayBlockEditorProps {
markdown: string;
@@ -291,19 +292,17 @@ export function EssayBlockEditor({ markdown, onChange, onSelectPhoto }: EssayBlo
{/* 2. PARAGRAPH BLOCK
The textarea shows `html` raw, as the journal editor does. It used
to strip the tags for display — `<strong>` to `**`, everything else
to nothing — while `onChange` wrote the stripped text straight back
into `html`. Italics, links, anything the markdown had produced was
destroyed by the act of editing the paragraph, and the two editors
disagreed about the same file. serializeJournalMarkdown() turns the
HTML back into markdown on save and handles `<em>` too, so there
was nothing to convert here in the first place. */}
The field shows the markdown source and writes the rendered HTML
back, as the journal editor does (InlineMarkdownField). An older
version stripped the tags for display and wrote the stripped text
straight back into `html`, destroying italics and links; the one
after that showed the raw HTML. */}
{block.type === 'paragraph' && (
<textarea
<InlineMarkdownField
multiline
rows={3}
value={block.html}
onChange={(e) => handleUpdateBlock(idx, { ...block, html: e.target.value })}
html={block.html}
onChange={(html) => handleUpdateBlock(idx, { ...block, html })}
placeholder="Write text paragraph... (**bold**, *italic* markdown supported)"
/>
)}
@@ -319,10 +318,11 @@ export function EssayBlockEditor({ markdown, onChange, onSelectPhoto }: EssayBlo
paddingLeft: '10px',
}}
>
<textarea
<InlineMarkdownField
multiline
rows={2}
value={block.text}
onChange={(e) => handleUpdateBlock(idx, { ...block, text: e.target.value })}
html={block.text}
onChange={(text) => handleUpdateBlock(idx, { ...block, text })}
placeholder="Quote text..."
style={{ fontStyle: 'italic', fontFamily: 'serif', fontSize: '1rem' }}
/>
@@ -411,11 +411,10 @@ export function EssayBlockEditor({ markdown, onChange, onSelectPhoto }: EssayBlo
</div>
</div>
<input
type="text"
value={block.caption || ''}
onChange={(e) =>
handleUpdateBlock(idx, { ...block, caption: e.target.value || undefined })
<InlineMarkdownField
html={block.caption}
onChange={(caption) =>
handleUpdateBlock(idx, { ...block, caption: caption || undefined })
}
placeholder="Photo caption (optional)"
/>
@@ -573,11 +572,10 @@ export function EssayBlockEditor({ markdown, onChange, onSelectPhoto }: EssayBlo
</div>
</div>
<input
type="text"
value={block.caption || ''}
onChange={(e) =>
handleUpdateBlock(idx, { ...block, caption: e.target.value || undefined })
<InlineMarkdownField
html={block.caption}
onChange={(caption) =>
handleUpdateBlock(idx, { ...block, caption: caption || undefined })
}
placeholder="Side-by-side caption (optional)"
/>
@@ -693,11 +691,10 @@ export function EssayBlockEditor({ markdown, onChange, onSelectPhoto }: EssayBlo
<IconPlus size={12} /> Add photo
</button>
<input
type="text"
value={block.caption || ''}
onChange={(e) =>
handleUpdateBlock(idx, { ...block, caption: e.target.value || undefined })
<InlineMarkdownField
html={block.caption}
onChange={(caption) =>
handleUpdateBlock(idx, { ...block, caption: caption || undefined })
}
placeholder="Grid caption (optional)"
/>
@@ -729,16 +726,13 @@ export function EssayBlockEditor({ markdown, onChange, onSelectPhoto }: EssayBlo
})
}
/>
<input
type="text"
value={item.value}
<InlineMarkdownField
html={item.value}
placeholder="Value, e.g. 21 km"
onChange={(e) =>
onChange={(value) =>
handleUpdateBlock(idx, {
...block,
items: block.items.map((it, i) =>
i === fIdx ? { ...it, value: e.target.value } : it,
),
items: block.items.map((it, i) => (i === fIdx ? { ...it, value } : it)),
})
}
/>
@@ -828,13 +822,12 @@ export function EssayBlockEditor({ markdown, onChange, onSelectPhoto }: EssayBlo
<option value="wide">Wide (one per row)</option>
</select>
</div>
<input
type="text"
value={block.caption || ''}
placeholder="Caption for the set (optional)"
onChange={(e) =>
handleUpdateBlock(idx, { ...block, caption: e.target.value || undefined })
<InlineMarkdownField
html={block.caption}
onChange={(caption) =>
handleUpdateBlock(idx, { ...block, caption: caption || undefined })
}
placeholder="Caption for the set (optional)"
/>
<span style={{ fontSize: '0.75rem', color: 'var(--admin-text-secondary)' }}>
Expanded into photo blocks when the page renders — the album&apos;s order, a
@@ -846,11 +839,10 @@ export function EssayBlockEditor({ markdown, onChange, onSelectPhoto }: EssayBlo
{/* 8. MAP BLOCK */}
{block.type === 'map' && (
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<input
type="text"
value={block.caption || ''}
onChange={(e) =>
handleUpdateBlock(idx, { ...block, caption: e.target.value || undefined })
<InlineMarkdownField
html={block.caption}
onChange={(caption) =>
handleUpdateBlock(idx, { ...block, caption: caption || undefined })
}
placeholder="Caption, e.g. Busan → Seoul (optional)"
/>