diff --git a/app/admin/RichEditor.tsx b/app/admin/RichEditor.tsx index e0dc1f7..99d2746 100644 --- a/app/admin/RichEditor.tsx +++ b/app/admin/RichEditor.tsx @@ -32,6 +32,86 @@ const thumbUrl = (url: string) => (url.startsWith('//') ? `https:${url}` : url); const AssetUrls = createContext>({}); +type CaptionState = 'loading' | 'idle' | 'saving' | 'saved' | 'error'; + +const PhotoCaption = ({ assetId }: { assetId: string }) => { + const [name, setName] = useState(''); + const [caption, setCaption] = useState(''); + const [state, setState] = useState('loading'); + const saved = useRef({ name: '', caption: '' }); + + useEffect(() => { + if (!assetId) return; + let cancelled = false; + fetch(`/api/admin/asset-caption?id=${encodeURIComponent(assetId)}`) + .then((res) => (res.ok ? res.json() : Promise.reject())) + .then((data: { name: string; caption: string }) => { + if (cancelled) return; + saved.current = data; + setName(data.name); + setCaption(data.caption); + setState('idle'); + }) + .catch(() => !cancelled && setState('idle')); + return () => { + cancelled = true; + }; + }, [assetId]); + + const save = async () => { + const next = { name: name.trim(), caption: caption.trim() }; + if (next.name === saved.current.name && next.caption === saved.current.caption) return; + setState('saving'); + try { + const res = await fetch('/api/admin/asset-caption', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id: assetId, ...next }), + }); + if (!res.ok) throw new Error(); + saved.current = next; + setState('saved'); + } catch { + setState('error'); + } + }; + + const field = + 'w-full rounded-md border border-gray-200 bg-white px-2.5 py-1.5 text-sm outline-none focus:border-black disabled:bg-gray-50'; + + return ( + + setName(event.target.value)} + onBlur={save} + onKeyDown={(event) => event.stopPropagation()} + /> + setCaption(event.target.value)} + onBlur={save} + onKeyDown={(event) => event.stopPropagation()} + /> + + {state === 'saving' && 'Saving...'} + {state === 'saved' && 'Saved.'} + {state === 'error' && Could not save. Try again.} + {(state === 'idle' || state === 'loading') && + 'Photos placed one after another show as a grid. Add a name and role to show them as a team.'} + + + ); +}; + const AssetImageView = ({ node, deleteNode, selected }: NodeViewProps) => { const urls = useContext(AssetUrls); const id = node.attrs.assetId as string; @@ -40,7 +120,7 @@ const AssetImageView = ({ node, deleteNode, selected }: NodeViewProps) => { return ( @@ -61,6 +141,8 @@ const AssetImageView = ({ node, deleteNode, selected }: NodeViewProps) => { )} + +