Files
memind/src/components/MindSpacePageEditablePreviewFrame.tsx
T
john 722b18326f feat(mindspace): 0630004 空间 UI、聊天连接、微信分享与 Agent 能力
含 MindSpace 三列布局与统计修复、聊天加载态与连接降级、平台页脚标记与 og:site_name 微信卡片、勾选资料删除 Agent 接口及内部话术过滤。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 18:08:06 +08:00

157 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useRef, useState } from 'react';
import { rewriteMindSpacePageDownloadLinks } from '../api/client';
import { bindIframeDocumentHeightSync, measureDocumentHeight } from '../utils/iframeContentHeight';
import {
buildEditablePreviewDocument,
MINDSPACE_PAGE_CONTENT_MESSAGE,
} from '../utils/mindspaceVisualEditor';
export function MindSpacePageEditablePreviewFrame({
pageId,
title,
content,
reloadKey,
minHeight = 680,
maxHeight = 12000,
className,
compact = false,
fillViewport = false,
showHint = true,
onFrameReady,
onContentChange,
}: {
pageId: string;
title: string;
content: string;
reloadKey: number;
minHeight?: number;
maxHeight?: number;
className?: string;
compact?: boolean;
fillViewport?: boolean;
showHint?: boolean;
onFrameReady?: (iframe: HTMLIFrameElement | null) => void;
onContentChange: (html: string) => void;
}) {
const scrollInsideFrame = compact || fillViewport;
const frameClassName =
className ?? (compact ? 'page-save-mini-page-frame' : 'mindspace-page-preview-frame');
const [previewFailed, setPreviewFailed] = useState(false);
const [displayContent, setDisplayContent] = useState(content);
const [srcdoc, setSrcdoc] = useState(() => buildEditablePreviewDocument(content));
const [height, setHeight] = useState(minHeight);
const iframeContentRef = useRef(content);
const iframeRef = useRef<HTMLIFrameElement>(null);
const syncHeightRef = useRef<(() => void) | null>(null);
useEffect(() => {
let cancelled = false;
const timer = window.setTimeout(() => {
void rewriteMindSpacePageDownloadLinks(pageId, content)
.then((html) => {
if (cancelled) return;
setDisplayContent(html);
})
.catch(() => {
if (cancelled) return;
setDisplayContent(content);
});
}, 120);
return () => {
cancelled = true;
window.clearTimeout(timer);
};
}, [pageId, content, reloadKey]);
useEffect(() => {
iframeContentRef.current = displayContent;
setSrcdoc(buildEditablePreviewDocument(displayContent));
setPreviewFailed(false);
setHeight(minHeight);
}, [displayContent, reloadKey, minHeight]);
useEffect(() => {
const onMessage = (event: MessageEvent) => {
if (event.source !== iframeRef.current?.contentWindow) return;
if (event.data?.type !== MINDSPACE_PAGE_CONTENT_MESSAGE) return;
const html = String(event.data.html ?? '');
if (!html.trim()) return;
iframeContentRef.current = html;
onContentChange(html);
syncHeightRef.current?.();
};
window.addEventListener('message', onMessage);
return () => window.removeEventListener('message', onMessage);
}, [onContentChange]);
useEffect(() => {
if (scrollInsideFrame) {
syncHeightRef.current = null;
return;
}
const iframe = iframeRef.current;
if (!iframe || !srcdoc) return;
const applyHeight = (nextHeight: number) => setHeight(nextHeight);
const unbind = bindIframeDocumentHeightSync(iframe, {
minHeight,
maxHeight,
onHeight: applyHeight,
});
syncHeightRef.current = () => {
try {
const doc = iframe.contentDocument;
if (!doc) return;
applyHeight(Math.min(measureDocumentHeight(doc, minHeight), maxHeight));
} catch {
applyHeight(minHeight);
}
};
return () => {
syncHeightRef.current = null;
unbind();
};
}, [maxHeight, minHeight, scrollInsideFrame, srcdoc]);
useEffect(() => {
onFrameReady?.(iframeRef.current);
return () => {
onFrameReady?.(null);
};
}, [onFrameReady, reloadKey, srcdoc, minHeight, maxHeight, scrollInsideFrame, compact, fillViewport]);
if (previewFailed) {
return <div className="mindspace-state mindspace-error">页面预览加载失败</div>;
}
return (
<>
{showHint ? (
<p className="mindspace-page-editable-hint">
<span className="mindspace-page-editable-legend-swatch mindspace-page-editable-legend-swatch-text" />
文字
<span className="mindspace-page-editable-legend-swatch mindspace-page-editable-legend-swatch-media" />
图片/媒体
<span className="mindspace-page-editable-legend-swatch mindspace-page-editable-legend-swatch-link" />
链接
<span className="mindspace-page-editable-legend-swatch mindspace-page-editable-legend-swatch-bg" />
背景 — 预览内左上角有完整图例,虚线框即可直接修改
</p>
) : null}
<iframe
ref={iframeRef}
title={`${title || '页面'} 预览`}
srcDoc={srcdoc}
sandbox="allow-scripts allow-downloads"
scrolling={scrollInsideFrame ? 'yes' : 'no'}
className={frameClassName}
style={scrollInsideFrame ? { height: '100%' } : { height: `${height}px` }}
onError={() => setPreviewFailed(true)}
/>
</>
);
}