Files
tolaria/src/components/useEditorModePositionSync.ts

131 lines
3.6 KiB
TypeScript
Raw Normal View History

2026-04-15 23:33:30 +02:00
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,
}
}
2026-04-15 23:33:30 +02:00
function useRawEditorRestoreEffect({
activeTabPath,
restoreTransitionRef,
2026-04-15 23:33:30 +02:00
rawMode,
}: {
activeTabPath: string | null
restoreTransitionRef: React.MutableRefObject<EditorModeRestoreTransition>
2026-04-15 23:33:30 +02:00
rawMode: boolean
}) {
useEffect(() => {
if (!rawMode || !restoreTransitionRef.current.rawRestore) return
2026-04-15 23:33:30 +02:00
let frame = 0
let attempts = 0
const tryRestore = () => {
const pendingState = restoreTransitionRef.current.rawRestore
2026-04-15 23:33:30 +02:00
if (!pendingState) return
if (restoreCodeMirrorView(document, pendingState)) {
restoreTransitionRef.current.rawRestore = null
2026-04-15 23:33:30 +02:00
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])
2026-04-15 23:33:30 +02:00
}
function useBlockNoteRestoreEffect({
activeTabPath,
editor,
restoreTransitionRef,
2026-04-15 23:33:30 +02:00
rawMode,
}: {
activeTabPath: string | null
editor: ReturnType<typeof useCreateBlockNote>
restoreTransitionRef: React.MutableRefObject<EditorModeRestoreTransition>
2026-04-15 23:33:30 +02:00
rawMode: boolean
}) {
useEffect(() => {
if (rawMode) return
2026-05-12 08:06:10 +02:00
let restoreFrame = 0
let canceled = false
const cancelPendingRestore = () => {
canceled = true
if (restoreFrame === 0) return
window.cancelAnimationFrame(restoreFrame)
restoreFrame = 0
}
2026-04-15 23:33:30 +02:00
const handleEditorTabSwapped = (event: Event) => {
const pendingSnapshot = restoreTransitionRef.current.richRestore
2026-04-15 23:33:30 +02:00
if (!activeTabPath || !pendingSnapshot) return
const customEvent = event as CustomEvent<{ path: string }>
if (customEvent.detail.path !== activeTabPath) return
2026-05-12 08:06:10 +02:00
if (restoreFrame !== 0) {
window.cancelAnimationFrame(restoreFrame)
}
restoreFrame = window.requestAnimationFrame(() => {
restoreFrame = 0
if (canceled) return
2026-04-15 23:33:30 +02:00
restoreBlockNoteView(editor, pendingSnapshot, document)
restoreTransitionRef.current.roundTripRawRestore = null
restoreTransitionRef.current.richRestore = null
2026-04-15 23:33:30 +02:00
})
}
window.addEventListener('laputa:editor-tab-swapped', handleEditorTabSwapped)
return () => {
2026-05-12 08:06:10 +02:00
cancelPendingRestore()
2026-04-15 23:33:30 +02:00
window.removeEventListener('laputa:editor-tab-swapped', handleEditorTabSwapped)
}
}, [activeTabPath, editor, restoreTransitionRef, rawMode])
2026-04-15 23:33:30 +02:00
}
export function useEditorModePositionSync({
activeTabPath,
editor,
restoreTransitionRef,
2026-04-15 23:33:30 +02:00
rawMode,
}: {
activeTabPath: string | null
editor: ReturnType<typeof useCreateBlockNote>
restoreTransitionRef: React.MutableRefObject<EditorModeRestoreTransition>
2026-04-15 23:33:30 +02:00
rawMode: boolean
}) {
useRawEditorRestoreEffect({ activeTabPath, restoreTransitionRef, rawMode })
useBlockNoteRestoreEffect({ activeTabPath, editor, restoreTransitionRef, rawMode })
2026-04-15 23:33:30 +02:00
}