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

@@ -2,17 +2,32 @@
import { useState, useMemo } from 'react';
import Image from 'next/image';
import dynamic from 'next/dynamic';
import { Lightbox, type LightboxWatermark } from '@/components/Lightbox';
import { ProofingProvider, useProofing } from '@/components/ProofingContext';
import { ProofingModal } from '@/components/ProofingModal';
import { useProofing } from '@/components/useProofing';
import { ProofingProvider, ProofingModal } from '@/components/ProofingLazy';
import { FadeIn } from '@/components/FadeIn';
import { LeafletMap, escapeHtml } from '@/components/LeafletMap';
import { escapeHtml } from '@/lib/escapeHtml';
import type { ParsedEssay, EssayBlock } from '@/lib/essay';
import { renderInlineMarkdown } from '@/lib/essay';
import { essayPhotoSequence } from '@/lib/essaySequence';
import type { PhotoItem } from './PhotoGrid';
import './essay.css';
import { useDictionary } from '@/components/I18nProvider';
import { formatJournalDate } from '@/lib/journalDate';
/**
* The map block is the only thing in an essay that needs Leaflet, and most
* essays have none. Imported statically, LeafletMap put its wrapper and
* Leaflet's stylesheet (render-blocking CSS) on every album, subpage and
* journal page. The map only ever drew on the client — Leaflet itself was
* already imported inside an effect — so the server renders the same empty
* container it rendered before, and the wrapper arrives with the map.
*/
const LeafletMap = dynamic(() => import('@/components/LeafletMap').then((m) => m.LeafletMap), {
ssr: false,
loading: () => <div className="essay-map" />,
});
interface EssayViewProps {
essay: ParsedEssay;
@@ -28,6 +43,8 @@ interface EssayViewProps {
proofing?: boolean;
/** Offer the "send by email" button in the proofing modal. */
allowMailto?: boolean;
/** `encodeEmail()` of the proofing recipient, when one is configured (#736). */
encodedMailto?: string;
}
function EssayViewContent({
@@ -389,6 +406,9 @@ function EssayViewContent({
}
};
// The story's photos that are selected, not the whole stored set (see PhotoGrid).
const selectedCount = proofing ? proofing.getSelectedTokens().length : 0;
return (
<div className="essay-container">
<header className="essay-header">
@@ -396,7 +416,9 @@ function EssayViewContent({
{displaySubtitle && <p className="essay-header__subtitle">{displaySubtitle}</p>}
{(essay.frontmatter.author || essay.frontmatter.date) && (
<div className="essay-header__meta">
{[essay.frontmatter.author, essay.frontmatter.date].filter(Boolean).join(' • ')}
{[essay.frontmatter.author, formatJournalDate(essay.frontmatter.date, t.dateLocale)]
.filter(Boolean)
.join(' • ')}
</div>
)}
</header>
@@ -429,9 +451,9 @@ function EssayViewContent({
</figure>
)}
<main>{essay.blocks.map(renderBlock)}</main>
<div>{essay.blocks.map(renderBlock)}</div>
{proofing && proofing.favorites.size > 0 && (
{proofing && selectedCount > 0 && (
<div
className="proofing-sticky-bar"
style={{
@@ -454,7 +476,7 @@ function EssayViewContent({
{/* A count, not the album grid's filter toggle: a story's layout is
authored, so there is nothing for "show favourites" to narrow. */}
<span style={{ fontSize: '0.85rem', fontWeight: 500, padding: '6px 4px' }}>
{t.proofing.selected(proofing.favorites.size)}
{t.proofing.selected(selectedCount)}
</span>
<button
type="button"
@@ -513,6 +535,7 @@ export function EssayView(props: EssayViewProps) {
// default key with every album (and story) on the site.
albumName={props.title || props.essay.frontmatter.title}
allowMailto={props.allowMailto ?? true}
encodedMailto={props.encodedMailto}
>
<EssayViewContent {...props} />
</ProofingProvider>