import { useCallback, useEffect, useRef, useState } from 'react'; import { useParams } from 'react-router-dom'; import EditorLayout from '../components/EditorLayout.jsx'; import ReadOnlyBanner from '../components/ReadOnlyBanner.jsx'; import SaveStatus from '../components/SaveStatus.jsx'; import StatusBadge from '../components/StatusBadge.jsx'; import VersionHistory from '../components/VersionHistory.jsx'; import { useEditLock } from '../hooks/useEditLock.js'; import { useServerAutosave } from '../hooks/useServerAutosave.js'; import { ApiError, fetchEditDocument, friendlyErrorMessage, } from '../lib/api.js'; import { downloadDocument } from '../lib/download.js'; import { parseOrgDocument } from '../lib/org/parseDocument.js'; import { STR } from '../lib/strings.js'; import LinkErrorPage from './LinkErrorPage.jsx'; export default function SharedEditPage() { const { token } = useParams(); const [doc, setDoc] = useState(null); const [title, setTitle] = useState(''); const [content, setContent] = useState(''); const [mode, setMode] = useState('markdown'); const [loadError, setLoadError] = useState(null); const [loading, setLoading] = useState(true); const [lockLost, setLockLost] = useState(false); const editorRef = useRef(null); const titleEditedRef = useRef(false); const { lockState, acquire, release, hasLock, lockToken, clientId } = useEditLock(token, !!doc && !loadError); const canEdit = hasLock && !lockLost; const { saveStatus, saveNow } = useServerAutosave({ editToken: token, clientId, lockToken, enabled: canEdit, title, mode, content, }); useEffect(() => { if (mode !== 'org' || titleEditedRef.current) return; const { title: orgTitle } = parseOrgDocument(content); if ( orgTitle && (title === STR.UNTITLED_DOCUMENT || title.trim() === '') ) { setTitle(orgTitle); } }, [content, mode, title]); useEffect(() => { let cancelled = false; (async () => { setLoading(true); setLoadError(null); titleEditedRef.current = false; try { const data = await fetchEditDocument(token); if (cancelled) return; setDoc(data); setTitle(data.title); setContent(data.content); setMode(data.mode); } catch (err) { if (!cancelled) setLoadError(err); } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, [token]); const lockRequestedRef = useRef(false); useEffect(() => { lockRequestedRef.current = false; }, [token]); useEffect(() => { if (!doc || loadError || lockRequestedRef.current) return; lockRequestedRef.current = true; acquire().catch(() => {}); }, [doc, loadError, acquire]); useEffect(() => { if (lockState.status === 'lost') { setLockLost(true); } if (saveStatus === 'no_permission') { setLockLost(true); } }, [lockState.status, saveStatus]); const handleSaveServer = useCallback(async () => { const ok = await saveNow(); if (!ok) setLockLost(true); }, [saveNow]); const handleRelease = useCallback(async () => { await release(); setLockLost(true); }, [release]); const handleDownload = useCallback(() => { downloadDocument(content, mode, title); }, [content, mode, title]); const handleVersionRestored = useCallback( (data) => { setTitle(data.title); setMode(data.mode); setContent(data.content); saveNow(); }, [saveNow], ); if (loading) { return (

{STR.LOADING_DOCUMENT}

); } if (loadError instanceof ApiError) { if (loadError.status === 410) { return ( ); } if (loadError.status === 404) { return ( ); } } if (loadError || !doc) { return ( ); } const saveLabel = mode === 'org' ? STR.DOWNLOAD_ORG : STR.DOWNLOAD_MD; const readOnly = !canEdit || lockState.status === 'blocked' || lockState.status === 'acquiring'; return (
{ titleEditedRef.current = true; setTitle(e.target.value); }} readOnly={readOnly} aria-label="Document title" /> {STR.BADGE_SHARED} {canEdit ? ( {STR.BADGE_EDITING} ) : ( {STR.BADGE_READONLY} )}

{canEdit ? STR.SHARED_EDIT : STR.SHARED_VIEW}

{canEdit && ( <> )}
{lockState.status === 'blocked' && ( )} {lockLost && ( )}
); }