"use client"; import { useEffect, useRef, useState, useCallback } from "react"; import { Copy, Check, WrapText } from "lucide-react"; import type { PreviewPluginProps } from "./types"; import { getLanguageFromMime, getLanguageFromFilename } from "@/lib/utils"; export default function CodePreview({ file, isEditing, onSave }: PreviewPluginProps) { const editorRef = useRef(null); // eslint-disable-next-line @typescript-eslint/no-explicit-any const viewRef = useRef(null); const [content, setContent] = useState(""); const [loading, setLoading] = useState(true); const [copied, setCopied] = useState(false); const [wordWrap, setWordWrap] = useState(false); const [modified, setModified] = useState(false); const saveTimeout = useRef>(); // Load content useEffect(() => { setLoading(true); fetch(`/api/files/${file.id}/preview`) .then((res) => res.text()) .then((text) => { setContent(text); setLoading(false); }) .catch(() => setLoading(false)); }, [file.id]); // Initialize CodeMirror useEffect(() => { if (loading || !editorRef.current) return; let destroyed = false; const initEditor = async () => { const { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, highlightActiveLine } = await import("@codemirror/view"); const { EditorState } = await import("@codemirror/state"); const { defaultKeymap, history, historyKeymap, indentWithTab } = await import("@codemirror/commands"); const { syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldGutter, foldKeymap, indentOnInput } = await import("@codemirror/language"); const { searchKeymap, highlightSelectionMatches } = await import("@codemirror/search"); const { oneDark } = await import("@codemirror/theme-one-dark"); if (destroyed || !editorRef.current) return; // Get language extension const lang = getLanguageFromMime(file.mimeType) || getLanguageFromFilename(file.name); // eslint-disable-next-line @typescript-eslint/no-explicit-any let langExtension: any = null; try { switch (lang) { case "javascript": case "typescript": { const { javascript } = await import("@codemirror/lang-javascript"); langExtension = javascript({ typescript: lang === "typescript", jsx: true }); break; } case "html": { const { html } = await import("@codemirror/lang-html"); langExtension = html(); break; } case "css": { const { css } = await import("@codemirror/lang-css"); langExtension = css(); break; } case "json": { const { json } = await import("@codemirror/lang-json"); langExtension = json(); break; } case "markdown": { const { markdown } = await import("@codemirror/lang-markdown"); langExtension = markdown(); break; } case "python": { const { python } = await import("@codemirror/lang-python"); langExtension = python(); break; } case "xml": { const { xml } = await import("@codemirror/lang-xml"); langExtension = xml(); break; } case "sql": { const { sql } = await import("@codemirror/lang-sql"); langExtension = sql(); break; } case "java": { const { java } = await import("@codemirror/lang-java"); langExtension = java(); break; } case "c": case "cpp": { const { cpp } = await import("@codemirror/lang-cpp"); langExtension = cpp(); break; } case "rust": { const { rust } = await import("@codemirror/lang-rust"); langExtension = rust(); break; } case "go": { const { go } = await import("@codemirror/lang-go"); langExtension = go(); break; } case "php": { const { php } = await import("@codemirror/lang-php"); langExtension = php(); break; } } } catch { // Language not available, continue without } const extensions = [ lineNumbers(), highlightActiveLineGutter(), highlightSpecialChars(), drawSelection(), highlightActiveLine(), bracketMatching(), foldGutter(), indentOnInput(), syntaxHighlighting(defaultHighlightStyle), highlightSelectionMatches(), history(), oneDark, keymap.of([ ...defaultKeymap, ...historyKeymap, ...foldKeymap, ...searchKeymap, indentWithTab, ]), EditorView.editable.of(isEditing), EditorState.readOnly.of(!isEditing), ]; if (langExtension) extensions.push(langExtension); if (wordWrap) { extensions.push(EditorView.lineWrapping); } if (isEditing && onSave) { extensions.push( EditorView.updateListener.of((update) => { if (update.docChanged) { setModified(true); if (saveTimeout.current) clearTimeout(saveTimeout.current); saveTimeout.current = setTimeout(() => { const text = update.state.doc.toString(); onSave(text, false).then(() => setModified(false)); }, 2000); } }), keymap.of([{ key: "Mod-s", run: (view) => { if (saveTimeout.current) clearTimeout(saveTimeout.current); const text = view.state.doc.toString(); onSave(text, false).then(() => setModified(false)); return true; }, }]), ); } // Clean up previous editor if (viewRef.current) viewRef.current.destroy(); editorRef.current.innerHTML = ""; const view = new EditorView({ state: EditorState.create({ doc: content, extensions }), parent: editorRef.current, }); viewRef.current = view; }; initEditor(); return () => { destroyed = true; if (viewRef.current) { viewRef.current.destroy(); viewRef.current = null; } }; }, [loading, content, isEditing, wordWrap, file.mimeType, file.name]); const copyContent = useCallback(() => { navigator.clipboard.writeText(content); setCopied(true); setTimeout(() => setCopied(false), 2000); }, [content]); if (loading) { return (
); } return (
{/* Toolbar */}
{getLanguageFromMime(file.mimeType) || getLanguageFromFilename(file.name)} {isEditing && modified && ( modifié )}
{/* Editor */}
); }