"use client"; import { useAtomValue, useSetAtom } from "jotai"; import { AlertCircle, XIcon } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import { closeEditorPanelAtom, editorPanelAtom } from "@/atoms/editor/editor-panel.atom"; import { PlateEditor } from "@/components/editor/plate-editor"; import { Button } from "@/components/ui/button"; import { Drawer, DrawerContent, DrawerHandle, DrawerTitle } from "@/components/ui/drawer"; import { useMediaQuery } from "@/hooks/use-media-query"; import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils"; interface EditorContent { document_id: number; title: string; document_type?: string; source_markdown: string; } function EditorPanelSkeleton() { return (
); } export function EditorPanelContent({ documentId, searchSpaceId, title, onClose, }: { documentId: number; searchSpaceId: number; title: string | null; onClose?: () => void; }) { const [editorDoc, setEditorDoc] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); const [editedMarkdown, setEditedMarkdown] = useState(null); const markdownRef = useRef(""); const initialLoadDone = useRef(false); const changeCountRef = useRef(0); const [displayTitle, setDisplayTitle] = useState(title || "Untitled"); useEffect(() => { let cancelled = false; setIsLoading(true); setError(null); setEditorDoc(null); setEditedMarkdown(null); initialLoadDone.current = false; changeCountRef.current = 0; const fetchContent = async () => { const token = getBearerToken(); if (!token) { redirectToLogin(); return; } try { const response = await authenticatedFetch( `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`, { method: "GET" } ); if (cancelled) return; if (!response.ok) { const errorData = await response .json() .catch(() => ({ detail: "Failed to fetch document" })); throw new Error(errorData.detail || "Failed to fetch document"); } const data = await response.json(); if (data.source_markdown === undefined || data.source_markdown === null) { setError( "This document does not have editable content. Please re-upload to enable editing." ); setIsLoading(false); return; } markdownRef.current = data.source_markdown; setDisplayTitle(data.title || title || "Untitled"); setEditorDoc(data); initialLoadDone.current = true; } catch (err) { if (cancelled) return; console.error("Error fetching document:", err); setError(err instanceof Error ? err.message : "Failed to fetch document"); } finally { if (!cancelled) setIsLoading(false); } }; fetchContent(); return () => { cancelled = true; }; }, [documentId, searchSpaceId, title]); const handleMarkdownChange = useCallback((md: string) => { markdownRef.current = md; if (!initialLoadDone.current) return; changeCountRef.current += 1; if (changeCountRef.current <= 1) return; setEditedMarkdown(md); }, []); const handleSave = useCallback(async () => { const token = getBearerToken(); if (!token) { toast.error("Please login to save"); redirectToLogin(); return; } setSaving(true); try { const response = await authenticatedFetch( `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/save`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ source_markdown: markdownRef.current }), } ); if (!response.ok) { const errorData = await response .json() .catch(() => ({ detail: "Failed to save document" })); throw new Error(errorData.detail || "Failed to save document"); } setEditorDoc((prev) => (prev ? { ...prev, source_markdown: markdownRef.current } : prev)); setEditedMarkdown(null); toast.success("Document saved! Reindexing in background..."); } catch (err) { console.error("Error saving document:", err); toast.error(err instanceof Error ? err.message : "Failed to save document"); } finally { setSaving(false); } }, [documentId, searchSpaceId]); return ( <>

{displayTitle}

{editedMarkdown !== null && (

Unsaved changes

)}
{onClose && ( )}
{isLoading ? ( ) : error || !editorDoc ? (

Failed to load document

{error || "An unknown error occurred"}

) : ( )}
); } function DesktopEditorPanel() { const panelState = useAtomValue(editorPanelAtom); const closePanel = useSetAtom(closeEditorPanelAtom); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") closePanel(); }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [closePanel]); if (!panelState.isOpen || !panelState.documentId || !panelState.searchSpaceId) return null; return (
); } function MobileEditorDrawer() { const panelState = useAtomValue(editorPanelAtom); const closePanel = useSetAtom(closeEditorPanelAtom); if (!panelState.documentId || !panelState.searchSpaceId) return null; return ( { if (!open) closePanel(); }} shouldScaleBackground={false} > {panelState.title || "Editor"}
); } export function EditorPanel() { const panelState = useAtomValue(editorPanelAtom); const isDesktop = useMediaQuery("(min-width: 1024px)"); if (!panelState.isOpen || !panelState.documentId) return null; if (isDesktop) { return ; } return ; } export function MobileEditorPanel() { const panelState = useAtomValue(editorPanelAtom); const isDesktop = useMediaQuery("(min-width: 1024px)"); if (isDesktop || !panelState.isOpen || !panelState.documentId) return null; return ; }