import { useState, useEffect, useCallback, useRef } from 'react'; import { toast } from 'sonner'; import { TipTapEditor } from './TipTapEditor'; import { FrontmatterEditor } from './FrontmatterEditor'; import { PublishDialog } from './PublishDialog'; import { PublishSuccessDialog } from './PublishSuccessDialog'; import { useFileContent, useUpdateFile } from '../../lib/hooks/useFileContent'; import { useBranchStatus, usePublish } from '../../lib/hooks/useBranch'; import { debounce } from '../../lib/utils/debounce'; import { Loading } from '../ui/Loading'; import type { PublishResponse } from '../../lib/api/branch'; interface EditorContainerProps { owner: string; repo: string; path: string; onClose?: () => void; } export function EditorContainer({ owner, repo, path, onClose }: EditorContainerProps) { const { data: fileData, isLoading, error } = useFileContent(owner, repo, path); const { data: branchStatus } = useBranchStatus(owner, repo); const updateFile = useUpdateFile(); const publish = usePublish(); const [content, setContent] = useState(''); const [frontmatter, setFrontmatter] = useState>({}); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [lastSaved, setLastSaved] = useState(null); const [isSaving, setIsSaving] = useState(false); const [showPublishDialog, setShowPublishDialog] = useState(false); const [showSuccessDialog, setShowSuccessDialog] = useState(false); const [publishResult, setPublishResult] = useState(); // Keep track of initial content to detect changes const initialContentRef = useRef<{ content: string; frontmatter: Record } | null>(null); // Load file content when data is available useEffect(() => { if (fileData) { setContent(fileData.content || ''); setFrontmatter(fileData.frontmatter || {}); initialContentRef.current = { content: fileData.content || '', frontmatter: fileData.frontmatter || {}, }; setHasUnsavedChanges(false); } }, [fileData]); // Auto-save function const saveChanges = useCallback(async () => { if (!hasUnsavedChanges) return; setIsSaving(true); try { await updateFile.mutateAsync({ owner, repo, path, content, frontmatter, }); setLastSaved(new Date()); setHasUnsavedChanges(false); } catch (error) { console.error('Failed to save:', error); toast.error('Failed to save changes', { description: error instanceof Error ? error.message : 'Please try again', }); } finally { setIsSaving(false); } }, [owner, repo, path, content, frontmatter, hasUnsavedChanges, updateFile]); // Debounced auto-save (2 seconds) const debouncedSave = useCallback( debounce(() => { saveChanges(); }, 2000), [saveChanges] ); // Handle content changes const handleContentChange = (newContent: string) => { setContent(newContent); setHasUnsavedChanges(true); debouncedSave(); }; // Handle frontmatter changes const handleFrontmatterChange = (newFrontmatter: Record) => { setFrontmatter(newFrontmatter); setHasUnsavedChanges(true); debouncedSave(); }; // Warn before leaving if there are unsaved changes useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { if (hasUnsavedChanges) { e.preventDefault(); e.returnValue = ''; } }; window.addEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, [hasUnsavedChanges]); // Handle publish const handlePublish = async (commitMessage: string, prTitle: string, prDescription: string) => { try { const result = await publish.mutateAsync({ owner, repo, commit_message: commitMessage, pr_title: prTitle, pr_description: prDescription, files: [path], }); setPublishResult(result); setShowPublishDialog(false); setShowSuccessDialog(true); setHasUnsavedChanges(false); toast.success('Pull request created successfully!', { description: `Branch: ${result.branch_name}`, }); } catch (error) { console.error('Failed to publish:', error); toast.error('Failed to publish changes', { description: error instanceof Error ? error.message : 'Please try again', }); } }; if (isLoading) { return (
); } if (error) { return (
Error Loading File
{error instanceof Error ? error.message : 'Unknown error'}
{onClose && ( )}
); } return (
{/* Header */}

{path.split('/').pop()}

{path}
{/* Save status */}
{isSaving ? ( Saving... ) : hasUnsavedChanges ? ( Unsaved changes ) : lastSaved ? ( Saved {lastSaved.toLocaleTimeString()} ) : null}
{/* Manual save button */} {/* Publish button */}
{/* Editor Content */}
{/* Publish Dialog */} setShowPublishDialog(false)} onPublish={handlePublish} branchStatus={branchStatus} currentFilePath={path} isPublishing={publish.isPending} /> {/* Success Dialog */} setShowSuccessDialog(false)} publishResult={publishResult} />
); }