'use client'; import { useState, useEffect, useCallback } from 'react'; import { parseEssayMarkdown, serializeEssayMarkdown, type ParsedEssay, type EssayBlock, } from '@/lib/essay'; import * as Icons from './Icons'; interface EssayBlockEditorProps { markdown: string; onChange: (newMarkdown: string) => void; onSelectPhoto?: (callback: (assetId: string) => void) => void; } export function EssayBlockEditor({ markdown, onChange, onSelectPhoto }: EssayBlockEditorProps) { const [essay, setEssay] = useState(() => parseEssayMarkdown(markdown || '# Title\n\nWrite your story here...') ); // Sync internal state when prop changes from outside (e.g. initial load) useEffect(() => { const currentSerialized = serializeEssayMarkdown(essay); if (markdown !== currentSerialized && markdown) { setEssay(parseEssayMarkdown(markdown)); } }, [markdown]); // eslint-disable-line react-hooks/exhaustive-deps const updateBlocks = useCallback( (newBlocks: EssayBlock[]) => { const updatedEssay: ParsedEssay = { ...essay, blocks: newBlocks, referencedAssetIds: Array.from( new Set( newBlocks.flatMap((b) => { if (b.type === 'photo') return [b.assetId]; if (b.type === 'photo-pair') return b.assetIds; return []; }) ) ), }; setEssay(updatedEssay); onChange(serializeEssayMarkdown(updatedEssay)); }, [essay, onChange] ); // Block manipulation helpers const handleMoveBlock = (index: number, direction: 'up' | 'down') => { const targetIdx = direction === 'up' ? index - 1 : index + 1; if (targetIdx < 0 || targetIdx >= essay.blocks.length) return; const newBlocks = [...essay.blocks]; const [moved] = newBlocks.splice(index, 1); newBlocks.splice(targetIdx, 0, moved); updateBlocks(newBlocks); }; const handleDeleteBlock = (index: number) => { const newBlocks = essay.blocks.filter((_, i) => i !== index); updateBlocks(newBlocks); }; const handleUpdateBlock = (index: number, updated: EssayBlock) => { const newBlocks = [...essay.blocks]; newBlocks[index] = updated; updateBlocks(newBlocks); }; const handleAddBlock = (type: EssayBlock['type']) => { let newBlock: EssayBlock; switch (type) { case 'heading': newBlock = { type: 'heading', level: 2, text: 'New Section Heading' }; break; case 'paragraph': newBlock = { type: 'paragraph', html: 'Enter text paragraph here...' }; break; case 'quote': newBlock = { type: 'quote', text: 'Enter quote text here...', author: 'Author' }; break; case 'photo': newBlock = { type: 'photo', assetId: 'sample-asset', caption: 'Photo caption', layout: 'contained', }; break; case 'photo-pair': newBlock = { type: 'photo-pair', assetIds: ['asset-1', 'asset-2'], caption: 'Side by side caption', }; break; } updateBlocks([...essay.blocks, newBlock]); }; return (
✨ Add Story Block:
{essay.blocks.length === 0 && (

No story blocks yet. Use the buttons above to build your photo essay!

)} {essay.blocks.map((block, idx) => (
{/* Block Header / Action Controls */}
Block {idx + 1}: {block.type}
{/* Render Block Form Controls */} {block.type === 'heading' && (
handleUpdateBlock(idx, { ...block, text: e.target.value })} placeholder="Section title..." style={{ flex: 1, padding: '6px 10px', borderRadius: '4px' }} />
)} {block.type === 'paragraph' && (