122 lines
3.7 KiB
TypeScript
122 lines
3.7 KiB
TypeScript
import React, { useEffect, useRef } from 'react'
|
|
import { useDebounceFn } from 'ahooks'
|
|
import { cleanDom } from '../lib/core/utils'
|
|
import { Html } from '../components/html-render/task-html'
|
|
import { useIframeMode } from '../hooks/useIframeMode'
|
|
import { useDiff } from '../hooks/useDiff'
|
|
import { useEditState } from '../hooks/useEditState'
|
|
import { FloatingToolbar } from '../../ppt-editor/FloatingToolbar'
|
|
import type { ArtifactEditState } from '../types'
|
|
import { saveMarkdown } from '../server'
|
|
import { TaskArtifact } from '@/components/nova-sdk'
|
|
|
|
export const HtmlWithEditMode: React.FC<{
|
|
taskId: string
|
|
taskArtifact: TaskArtifact
|
|
onStateChange?: (state: ArtifactEditState) => void
|
|
content: string
|
|
isDoc?: boolean
|
|
|
|
}> = props => {
|
|
const { taskId, isDoc ,taskArtifact, content, onStateChange } = props
|
|
const htmlIframe = useRef<HTMLIFrameElement>(null)
|
|
const editState = useEditState()
|
|
|
|
const saveHandler = async (type: 'auto' | 'manual' = 'auto', callback?: () => void) => {
|
|
if (editState.isSaving) return
|
|
if (!htmlIframe.current) return
|
|
const iframeDoc = htmlIframe.current.contentDocument?.documentElement
|
|
if (!iframeDoc) return
|
|
|
|
editState.setSaveType(type)
|
|
editState.setIsSaving(true)
|
|
try {
|
|
const srcDoc = cleanDom(iframeDoc)
|
|
await saveMarkdown({
|
|
task_id: taskId,
|
|
content: srcDoc,
|
|
path: taskArtifact.path,
|
|
})
|
|
callback?.()
|
|
} catch (e) {
|
|
console.error(e)
|
|
} finally {
|
|
editState.setIsSaving(false)
|
|
editState.setSaveType(null)
|
|
}
|
|
}
|
|
|
|
const handleSave = useDebounceFn(saveHandler, { wait: 1000 })
|
|
|
|
const manualSave = () => {
|
|
handleSave.cancel()
|
|
saveHandler('manual')
|
|
}
|
|
|
|
const useIframeReturn = useIframeMode(taskId, htmlIframe, {
|
|
enableGlobalContentEditable: isDoc,
|
|
onContentChange: content => {
|
|
handleSave.run('auto')
|
|
},
|
|
onHistoryChange: (_state, instance) => {
|
|
editState.handleHistoryChangeEvent(instance)
|
|
// 同时向上传递状态,保持兼容性
|
|
if (onStateChange && instance) {
|
|
const canRedo = instance.EditorRegistry.canRedo()
|
|
const canUndo = instance.EditorRegistry.canUndo()
|
|
onStateChange({
|
|
canRedo,
|
|
canUndo,
|
|
redo: () => instance.EditorRegistry.redo(),
|
|
undo: () => instance.EditorRegistry.undo(),
|
|
})
|
|
}
|
|
},
|
|
})
|
|
|
|
useEffect(() => {
|
|
const saveHandler = (callback?: () => void) => {
|
|
if (!useIframeReturn.loadSuccess) {
|
|
callback?.()
|
|
return
|
|
}
|
|
if (!htmlIframe.current) return
|
|
const iframeDoc = htmlIframe.current.contentDocument?.documentElement
|
|
if (iframeDoc) {
|
|
const srcDoc = cleanDom(iframeDoc)
|
|
handleSave.run('auto', callback)
|
|
}
|
|
}
|
|
// eventBus.on('html-edit-save', saveHandler)
|
|
return () => {
|
|
// eventBus.off('html-edit-save', saveHandler)
|
|
}
|
|
}, [useIframeReturn.loadSuccess])
|
|
|
|
// doc编辑与web编辑差异逻辑
|
|
useDiff(useIframeReturn, isDoc)
|
|
|
|
return (
|
|
<div className="relative h-full ">
|
|
{/* 悬浮工具栏 */}
|
|
|
|
<Html
|
|
className={`opacity-0 ${'opacity-100'} h-full`}
|
|
content={content}
|
|
ref={htmlIframe}
|
|
/>
|
|
<div style={{ display: 'flex', justifyContent: 'center', padding: '12px 0 12px', zIndex: 100, position: 'absolute', left: 0, right: 0, top: 80 }}>
|
|
<FloatingToolbar
|
|
canUndo={editState.canUndo}
|
|
canRedo={editState.canRedo}
|
|
onUndo={() => editState.undo.current()}
|
|
onRedo={() => editState.redo.current()}
|
|
onSave={manualSave}
|
|
isSaving={editState.isSaving}
|
|
saveType={editState.saveType}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|