193 lines
6.4 KiB
TypeScript
193 lines
6.4 KiB
TypeScript
/**
|
|
* Starting-point block structures offered in the Journal Studio's Create
|
|
* modal, as an alternative to an empty editor. Client-safe (no `fs`), same
|
|
* tier as `lib/journal.ts` — importable directly from `JournalStudio.tsx`.
|
|
*
|
|
* Every photo, pair and grid placeholder leaves its `assetId` as `''`:
|
|
* `parseJournalMarkdown` round-trips that as an unfilled placeholder (see the
|
|
* comment on the image regex in `lib/journal.ts`), and the block editor
|
|
* already renders it as a "+ Pick" tile.
|
|
*/
|
|
|
|
import type { JournalBlock } from './journal';
|
|
|
|
export interface JournalTemplate {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
blocks: JournalBlock[];
|
|
}
|
|
|
|
type PhotoLayout = Extract<JournalBlock, { type: 'photo' }>['layout'];
|
|
|
|
// Captions and authors default to `undefined`, not `''`: that is what
|
|
// `parseJournalMarkdown` produces for an empty `(...)` or a missing `-- author`,
|
|
// so a template block must start that way too or the round-trip test sees a
|
|
// reshaped block on first load.
|
|
const photo = (layout: PhotoLayout, caption?: string): JournalBlock => ({
|
|
type: 'photo',
|
|
assetId: '',
|
|
layout,
|
|
caption: caption || undefined,
|
|
});
|
|
|
|
const pair = (caption?: string): JournalBlock => ({
|
|
type: 'photo-pair',
|
|
assetIds: ['', ''],
|
|
caption: caption || undefined,
|
|
});
|
|
|
|
const grid = (count: number, caption?: string): JournalBlock => ({
|
|
type: 'photo-grid',
|
|
assetIds: Array.from({ length: count }, () => ''),
|
|
caption: caption || undefined,
|
|
});
|
|
|
|
// Quote text must not contain ` -- `: that is the author separator.
|
|
const quote = (text: string, author?: string): JournalBlock => ({
|
|
type: 'quote',
|
|
text,
|
|
author: author || undefined,
|
|
});
|
|
|
|
// Values are placeholders too — an empty value would be dropped on save.
|
|
const facts = (...rows: Array<[label: string, placeholder: string]>): JournalBlock => ({
|
|
type: 'facts',
|
|
items: rows.map(([label, value]) => ({ label, value })),
|
|
});
|
|
|
|
// Templates opt into "every geotagged photo of the entry"; named stops are
|
|
// added in the studio, where a typed point is a label and two numbers.
|
|
const map = (caption?: string): JournalBlock => ({
|
|
type: 'map',
|
|
caption: caption || undefined,
|
|
line: true,
|
|
items: [{ kind: 'all-photos' }],
|
|
});
|
|
|
|
// An album block with no album yet: the studio shows "Pick album".
|
|
const album = (caption?: string): JournalBlock => ({
|
|
type: 'album',
|
|
albumId: '',
|
|
layout: 'grid',
|
|
...(caption ? { caption } : {}),
|
|
});
|
|
|
|
const heading = (text: string, level = 2): JournalBlock => ({ type: 'heading', level, text });
|
|
|
|
const paragraph = (text: string): JournalBlock => ({ type: 'paragraph', html: text });
|
|
|
|
export const JOURNAL_TEMPLATES: JournalTemplate[] = [
|
|
{
|
|
id: 'wedding',
|
|
name: 'Wedding',
|
|
description:
|
|
'Date and venue up front, one hero image, a line from the vows, the party as a grid.',
|
|
blocks: [
|
|
paragraph('[Who, where, and when — a sentence or two.]'),
|
|
facts(['Date', '[The date]'], ['Venue', '[Where it took place]']),
|
|
photo('fullbleed', '[The image that carries the whole day.]'),
|
|
heading('Getting Ready'),
|
|
pair('[Rings and dress, side by side.]'),
|
|
heading('Ceremony'),
|
|
photo('wide', '[The vows, the walk, the kiss.]'),
|
|
quote('[The line from the day you want remembered.]', '[Who said it]'),
|
|
heading('Reception'),
|
|
grid(3, '[First dance, the speeches, the party.]'),
|
|
paragraph('[A closing line — no need to wrap it up neatly.]'),
|
|
],
|
|
},
|
|
{
|
|
id: 'hiking',
|
|
name: 'Hiking / Outdoor',
|
|
description: 'Distance and elevation as facts, the trail as a grid, the route on a map.',
|
|
blocks: [
|
|
paragraph('[Route and conditions — keep it factual.]'),
|
|
facts(['Distance', '[21 km]'], ['Elevation', '[1,240 m]'], ['Duration', '[6 h]']),
|
|
photo('fullbleed', '[The landscape that sets the scene.]'),
|
|
heading('The Trail'),
|
|
grid(3, '[Along the way.]'),
|
|
photo('wide', '[The summit, or wherever it ended.]'),
|
|
map('[The route, from the geotagged photos above.]'),
|
|
],
|
|
},
|
|
{
|
|
id: 'travel',
|
|
name: 'Travel',
|
|
description: 'The trip on a map, then one stop per section with two photos each.',
|
|
blocks: [
|
|
paragraph('[Where, and over what dates.]'),
|
|
map('[Where the trip went, e.g. Busan → Seoul.]'),
|
|
heading('[Day or place 1]'),
|
|
paragraph('[A short note on this stop.]'),
|
|
pair(),
|
|
heading('[Day or place 2]'),
|
|
paragraph('[A short note on this stop.]'),
|
|
pair(),
|
|
heading('[Day or place 3]'),
|
|
paragraph('[A short note on this stop.]'),
|
|
pair(),
|
|
],
|
|
},
|
|
{
|
|
id: 'birthday',
|
|
name: 'Birthday / Family',
|
|
description: 'Almost no text — the whole album as a grid and one full-bleed frame.',
|
|
blocks: [
|
|
paragraph('[One personal sentence about the day.]'),
|
|
album('[The day, as it happened.]'),
|
|
photo('fullbleed', '[The one moment worth a full-bleed frame.]'),
|
|
],
|
|
},
|
|
{
|
|
id: 'portrait-session',
|
|
name: 'Portrait Session',
|
|
description: 'Solo wide frames with room to breathe, and a line in their own words.',
|
|
blocks: [
|
|
paragraph('[Who this session is with, and the idea behind it, in a sentence.]'),
|
|
photo('wide'),
|
|
quote('[Something they said during the session.]', '[Their name]'),
|
|
photo('wide'),
|
|
photo('wide'),
|
|
],
|
|
},
|
|
{
|
|
id: 'behind-the-scenes',
|
|
name: 'Behind the Scenes',
|
|
description: 'Setup and shoot as a pair, then the finished result to land on.',
|
|
blocks: [
|
|
paragraph('[What the assignment or setup was.]'),
|
|
pair('[Setting up, and mid-shoot.]'),
|
|
photo('contained', '[Another making-of moment.]'),
|
|
heading('The Result'),
|
|
photo('fullbleed', '[The finished shot.]'),
|
|
],
|
|
},
|
|
{
|
|
id: 'pets',
|
|
name: 'Pets',
|
|
description: 'Candid pairs, one close-up portrait with personality.',
|
|
blocks: [
|
|
paragraph('[Who they are — name, and whatever makes them them.]'),
|
|
pair(),
|
|
photo('wide', '[A close-up — the one with personality.]'),
|
|
pair(),
|
|
],
|
|
},
|
|
{
|
|
id: 'new-series',
|
|
name: 'New Series',
|
|
description: 'Opens on a statement, then photo-and-text pairs introducing the project.',
|
|
blocks: [
|
|
quote('[One sentence on why this series exists.]'),
|
|
photo('wide'),
|
|
paragraph('[A line about this image.]'),
|
|
photo('wide'),
|
|
paragraph('[A line about this image.]'),
|
|
photo('wide'),
|
|
paragraph('[A line about this image.]'),
|
|
paragraph('[More to come — or however this should close.]'),
|
|
],
|
|
},
|
|
];
|