117 lines
3.4 KiB
TypeScript
117 lines
3.4 KiB
TypeScript
import type { RefObject } from 'react';
|
|
import { createRef, useCallback, useEffect, useRef } from 'react';
|
|
import { useEditorCommands } from '~/.client/hooks';
|
|
import type { DocumentProperties, Editor } from '~/types/editor';
|
|
import { EditorController } from './EditorController';
|
|
import { EditorRender } from './EditorRender';
|
|
import { PageRender, type PageRenderRef } from './PageRender';
|
|
|
|
export interface EditorComponentProps {
|
|
documents?: Record<string, DocumentProperties>;
|
|
currentPage?: string;
|
|
onReady?: (editor: Editor) => void;
|
|
onLoad?: () => Promise<void>;
|
|
onContentChange?: (pageName: string, html: string) => void;
|
|
onSave?: (pageName: string, html: string) => Promise<void> | void;
|
|
}
|
|
|
|
export function EditorComponent(props: EditorComponentProps) {
|
|
const { documents = {}, currentPage, onReady, onLoad, onContentChange, onSave } = props;
|
|
|
|
const controllerRef = useRef<EditorController | null>(null);
|
|
const lastContentRef = useRef<Record<string, string>>({});
|
|
const pageRefsRef = useRef<Record<string, RefObject<PageRenderRef>>>({});
|
|
const currentPageRef = useRef<string | undefined>(currentPage);
|
|
|
|
useEditorCommands(controllerRef);
|
|
|
|
useEffect(() => {
|
|
Object.keys(documents).forEach((docName) => {
|
|
if (!pageRefsRef.current[docName]) {
|
|
pageRefsRef.current[docName] = createRef<PageRenderRef>();
|
|
}
|
|
});
|
|
}, [documents]);
|
|
|
|
useEffect(() => {
|
|
if (!controllerRef.current) {
|
|
controllerRef.current = new EditorController({
|
|
getContentElement,
|
|
getIframeElement,
|
|
});
|
|
|
|
setTimeout(() => {
|
|
if (controllerRef.current && onReady) {
|
|
onReady(controllerRef.current);
|
|
}
|
|
}, 0);
|
|
}
|
|
}, [onReady]);
|
|
|
|
useEffect(() => {
|
|
currentPageRef.current = currentPage;
|
|
}, [currentPage]);
|
|
|
|
const getContentElement = useCallback((): HTMLElement | null => {
|
|
const currentPageName = currentPageRef.current ?? 'index';
|
|
return pageRefsRef.current[currentPageName]?.current?.element ?? null;
|
|
}, [pageRefsRef]);
|
|
|
|
const getIframeElement = useCallback((): HTMLIFrameElement | null => {
|
|
const currentPageName = currentPageRef.current ?? 'index';
|
|
return pageRefsRef.current[currentPageName]?.current?.iframe ?? null;
|
|
}, [pageRefsRef]);
|
|
|
|
/**
|
|
* 执行保存
|
|
* @param html 要保存的 HTML 内容
|
|
*/
|
|
const handleSave = useCallback(
|
|
(pageName: string, html: string): void => {
|
|
if (lastContentRef.current[pageName] === html) {
|
|
return;
|
|
}
|
|
|
|
lastContentRef.current[pageName] = html;
|
|
if (onSave) {
|
|
onSave(pageName, html);
|
|
}
|
|
},
|
|
[onSave],
|
|
);
|
|
|
|
const handleContentUpdate = useCallback(
|
|
(pageName: string, html: string): void => {
|
|
if (lastContentRef.current[pageName] === html) {
|
|
return;
|
|
}
|
|
|
|
if (onContentChange) {
|
|
onContentChange(pageName, html);
|
|
}
|
|
},
|
|
[onContentChange],
|
|
);
|
|
|
|
const handleMount = useCallback(async (): Promise<void> => {
|
|
if (onLoad) {
|
|
await onLoad();
|
|
}
|
|
}, [onLoad]);
|
|
|
|
return (
|
|
<EditorRender onMount={handleMount}>
|
|
{Object.values(documents).map((document) => (
|
|
<PageRender
|
|
isCurrentPage={document.name === currentPage}
|
|
ref={pageRefsRef.current[document.name]}
|
|
key={document.name}
|
|
document={document}
|
|
onUpdate={handleContentUpdate}
|
|
onSave={handleSave}
|
|
/>
|
|
))}
|
|
</EditorRender>
|
|
);
|
|
}
|