Files
immich-folio/lib/essaySource.ts
Ralksta ad8b09700b 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>
2026-09-29 22:50:30 +02:00

57 lines
2.1 KiB
TypeScript

/**
* Server-only helpers for the subpage essay (story) path in
* `app/[...path]/page.tsx`.
*
* A subpage can borrow a journal entry as its essay via `essayFile: <slug>`.
* That entry keeps its own `draft` and `password`, and they have to hold here
* exactly as they do at `/journal/<slug>` — otherwise the subpage becomes a
* second, ungated door into the same content.
*/
import type { ImmichAsset } from './immich';
import { sanitizeHtml, type ParsedJournal } from './journal';
import { assetCaption } from './urls';
import { isAuthenticated } from './auth';
import { loadEssayFromFile } from './admin/journal-service';
export type EssayFileResult =
/** Readable by this visitor. */
| { status: 'open'; parsed: ParsedJournal }
/** No such entry, or a draft seen by a non-admin — both render as absent. */
| { status: 'missing' }
/**
* The entry has a password this visitor has not entered. No title: the
* journal index hides a locked entry by name, so the gate must not show it.
*/
| { status: 'locked' };
export function resolveEssayFile(
slug: string,
visitor: { isAdmin: boolean; getCookie: (name: string) => string | undefined },
): EssayFileResult {
const parsed = loadEssayFromFile(slug);
if (!parsed) return { status: 'missing' };
const { frontmatter } = parsed;
if (frontmatter.draft && !visitor.isAdmin) return { status: 'missing' };
if (frontmatter.password && !isAuthenticated(slug, visitor.getCookie, 'journal')) {
return { status: 'locked' };
}
return { status: 'open', parsed };
}
/**
* Caption for a photo in the essay generated from a subpage's albums.
*
* EssayView renders captions as HTML, because authored captions arrive already
* rendered by `renderInlineMarkdown`. An Immich description has not been
* through that, so it is escaped here — and it follows the `exif.caption`
* toggle like every other place a description is shown.
*/
export function generatedEssayCaption(
asset: Pick<ImmichAsset, 'exifInfo'>,
showCaption: boolean,
): string | undefined {
const caption = assetCaption(asset, showCaption);
return caption ? sanitizeHtml(caption) : undefined;
}