132 lines
3.6 KiB
TypeScript
132 lines
3.6 KiB
TypeScript
import { useEffect } from 'react'
|
|
import type { useCreateBlockNote } from '@blocknote/react'
|
|
import {
|
|
restoreBlockNoteView,
|
|
restoreCodeMirrorView,
|
|
type CodeMirrorRestoreState,
|
|
type RawEditorPositionSnapshot,
|
|
} from './editorModePosition'
|
|
|
|
const MAX_RAW_RESTORE_ATTEMPTS = 5
|
|
|
|
export interface EditorModeRestoreTransition {
|
|
rawRestore: CodeMirrorRestoreState | null
|
|
roundTripRawRestore: { path: string; state: CodeMirrorRestoreState } | null
|
|
richRestore: RawEditorPositionSnapshot | null
|
|
}
|
|
|
|
export function createEditorModeRestoreTransition(): EditorModeRestoreTransition {
|
|
return {
|
|
rawRestore: null,
|
|
roundTripRawRestore: null,
|
|
richRestore: null,
|
|
}
|
|
}
|
|
|
|
function useRawEditorRestoreEffect({
|
|
activeTabPath,
|
|
restoreTransitionRef,
|
|
rawMode,
|
|
}: {
|
|
activeTabPath: string | null
|
|
restoreTransitionRef: React.MutableRefObject<EditorModeRestoreTransition>
|
|
rawMode: boolean
|
|
}) {
|
|
useEffect(() => {
|
|
void activeTabPath
|
|
if (!rawMode || !restoreTransitionRef.current.rawRestore) return
|
|
|
|
let frame = 0
|
|
let attempts = 0
|
|
|
|
const tryRestore = () => {
|
|
const pendingState = restoreTransitionRef.current.rawRestore
|
|
if (!pendingState) return
|
|
if (restoreCodeMirrorView(document, pendingState)) {
|
|
restoreTransitionRef.current.rawRestore = null
|
|
return
|
|
}
|
|
attempts += 1
|
|
if (attempts < MAX_RAW_RESTORE_ATTEMPTS) {
|
|
frame = window.requestAnimationFrame(tryRestore)
|
|
}
|
|
}
|
|
|
|
frame = window.requestAnimationFrame(tryRestore)
|
|
return () => {
|
|
if (frame !== 0) {
|
|
window.cancelAnimationFrame(frame)
|
|
}
|
|
}
|
|
}, [activeTabPath, restoreTransitionRef, rawMode])
|
|
}
|
|
|
|
function useBlockNoteRestoreEffect({
|
|
activeTabPath,
|
|
editor,
|
|
restoreTransitionRef,
|
|
rawMode,
|
|
}: {
|
|
activeTabPath: string | null
|
|
editor: ReturnType<typeof useCreateBlockNote>
|
|
restoreTransitionRef: React.MutableRefObject<EditorModeRestoreTransition>
|
|
rawMode: boolean
|
|
}) {
|
|
useEffect(() => {
|
|
if (rawMode) return
|
|
|
|
let restoreFrame = 0
|
|
let canceled = false
|
|
|
|
const cancelPendingRestore = () => {
|
|
canceled = true
|
|
if (restoreFrame === 0) return
|
|
|
|
window.cancelAnimationFrame(restoreFrame)
|
|
restoreFrame = 0
|
|
}
|
|
|
|
const handleEditorTabSwapped = (event: Event) => {
|
|
const pendingSnapshot = restoreTransitionRef.current.richRestore
|
|
if (!activeTabPath || !pendingSnapshot) return
|
|
|
|
const customEvent = event as CustomEvent<{ path: string }>
|
|
if (customEvent.detail.path !== activeTabPath) return
|
|
|
|
if (restoreFrame !== 0) {
|
|
window.cancelAnimationFrame(restoreFrame)
|
|
}
|
|
|
|
restoreFrame = window.requestAnimationFrame(() => {
|
|
restoreFrame = 0
|
|
if (canceled) return
|
|
|
|
restoreBlockNoteView(editor, pendingSnapshot, document)
|
|
restoreTransitionRef.current.roundTripRawRestore = null
|
|
restoreTransitionRef.current.richRestore = null
|
|
})
|
|
}
|
|
|
|
window.addEventListener('laputa:editor-tab-swapped', handleEditorTabSwapped)
|
|
return () => {
|
|
cancelPendingRestore()
|
|
window.removeEventListener('laputa:editor-tab-swapped', handleEditorTabSwapped)
|
|
}
|
|
}, [activeTabPath, editor, restoreTransitionRef, rawMode])
|
|
}
|
|
|
|
export function useEditorModePositionSync({
|
|
activeTabPath,
|
|
editor,
|
|
restoreTransitionRef,
|
|
rawMode,
|
|
}: {
|
|
activeTabPath: string | null
|
|
editor: ReturnType<typeof useCreateBlockNote>
|
|
restoreTransitionRef: React.MutableRefObject<EditorModeRestoreTransition>
|
|
rawMode: boolean
|
|
}) {
|
|
useRawEditorRestoreEffect({ activeTabPath, restoreTransitionRef, rawMode })
|
|
useBlockNoteRestoreEffect({ activeTabPath, editor, restoreTransitionRef, rawMode })
|
|
}
|