import { useEffect, useRef, useState } from 'react'; import { fetchMindSpacePageDraftPreview } from '../api/client'; import { bindIframeDocumentHeightSync } from '../utils/iframeContentHeight'; import { applyPageChangeHighlight } from '../utils/mindspacePageChangeHighlight'; export type MindSpacePreviewChangeHighlight = { revision: number; previousTitle: string; previousContent: string; }; export function MindSpacePageDraftPreviewFrame({ pageId, title, summary, content, templateId, reloadKey = 0, liveUpdate = true, minHeight = 680, maxHeight = 12000, className = 'mindspace-page-preview-frame', changeHighlight = null, onFrameReady, }: { pageId: string; title: string; summary: string; content: string; templateId: string; reloadKey?: number; liveUpdate?: boolean; minHeight?: number; maxHeight?: number; className?: string; changeHighlight?: MindSpacePreviewChangeHighlight | null; onFrameReady?: (iframe: HTMLIFrameElement | null) => void; }) { const iframeRef = useRef(null); const [srcdoc, setSrcdoc] = useState(''); const [height, setHeight] = useState(minHeight); const [initialLoading, setInitialLoading] = useState(true); const [error, setError] = useState(null); const requestRef = useRef(0); const hasRenderedRef = useRef(false); const appliedHighlightRevisionRef = useRef(null); useEffect(() => { const requestId = requestRef.current + 1; requestRef.current = requestId; if (!hasRenderedRef.current) setInitialLoading(true); setError(null); const timer = window.setTimeout(() => { void fetchMindSpacePageDraftPreview(pageId, { title, summary, content, templateId }) .then((html) => { if (requestRef.current !== requestId) return; setSrcdoc(html); hasRenderedRef.current = true; setInitialLoading(false); }) .catch((err) => { if (requestRef.current !== requestId) return; setError(err instanceof Error ? err.message : '预览加载失败'); setInitialLoading(false); }); }, hasRenderedRef.current ? 450 : 120); return () => window.clearTimeout(timer); }, liveUpdate ? [pageId, title, summary, content, templateId, reloadKey] : [pageId, reloadKey, templateId]); useEffect(() => { setHeight(minHeight); }, [srcdoc, reloadKey, minHeight]); useEffect(() => { onFrameReady?.(iframeRef.current); return () => { onFrameReady?.(null); }; }, [onFrameReady, srcdoc, reloadKey]); useEffect(() => { const iframe = iframeRef.current; if (!iframe || !srcdoc) return; const applyHeight = (nextHeight: number) => { setHeight(nextHeight); if ( changeHighlight && appliedHighlightRevisionRef.current !== changeHighlight.revision ) { try { const doc = iframe.contentDocument; if (!doc) return; appliedHighlightRevisionRef.current = changeHighlight.revision; applyPageChangeHighlight(doc, { previousTitle: changeHighlight.previousTitle, nextTitle: title, previousContent: changeHighlight.previousContent, nextContent: content, }); } catch { // ignore highlight errors in sandboxed preview } } }; return bindIframeDocumentHeightSync(iframe, { minHeight, maxHeight, onHeight: applyHeight, }); }, [changeHighlight, content, maxHeight, minHeight, srcdoc, title]); if (error) { return
{error}
; } return ( <> {initialLoading && !srcdoc ? (
正在加载预览…
) : null}