cudr_slate-collaborative/src/client/useCursor.ts
2022-02-17 18:00:11 -05:00

101 lines
2.4 KiB
TypeScript

import { useState, useCallback, useEffect, useMemo } from 'react'
import { Text, Range, Path, NodeEntry } from 'slate'
import { toJS, Cursor, Cursors } from '../bridge/index'
import useMounted from './useMounted'
import { AutomergeEditor } from './interfaces'
const useCursor = (
e: AutomergeEditor
): { decorate: (entry: NodeEntry) => Range[]; cursors: Cursor[] } => {
const [cursorData, setCursorData] = useState<Cursor[]>([])
const mountedRef = useMounted()
useEffect(() => {
e.onCursor = (data: Cursors) => {
if (!mountedRef.current) return
const ranges: Cursor[] = []
// If the cursor data is null or undefined, unset all active cursors
if (!data) {
setCursorData(ranges)
return
}
try {
const cursors = toJS(data)
for (let cursor in cursors) {
if (cursor !== e.clientId && cursors[cursor]) {
ranges.push(JSON.parse(cursors[cursor]))
}
}
// only update state if this component is still mounted
if (mountedRef.current) {
setCursorData(ranges)
}
} catch (err) {
e.handleError(err, {
type: 'onCursor',
data,
ranges
})
}
}
}, [])
const cursors = useMemo<Cursor[]>(() => cursorData, [cursorData])
const decorate = useCallback(
([node, path]: NodeEntry) => {
const ranges: Range[] = []
if (Text.isText(node) && cursors?.length) {
cursors.forEach(cursor => {
if (Range.includes(cursor, path)) {
const { focus, anchor, isForward } = cursor
const isFocusNode = Path.equals(focus.path, path)
const isAnchorNode = Path.equals(anchor.path, path)
ranges.push({
...cursor,
isCaret: isFocusNode,
anchor: {
path,
offset: isAnchorNode
? anchor.offset
: isForward
? 0
: node.text.length
},
focus: {
path,
offset: isFocusNode
? focus.offset
: isForward
? node.text.length
: 0
}
})
}
})
}
return ranges
},
[cursors]
)
return {
cursors,
decorate
}
}
export default useCursor