import { BasePoint , BaseRange , Node , Point , Text } from 'slate' ; import * as Y from 'yjs' ; import { InsertDelta , RelativeRange , TextRange } from '../model/types' ; import { getInsertDeltaLength , yTextToInsertDelta } from './delta' ; import { getSlatePath , getYTarget , yOffsetToSlateOffsets } from './location' ; import { assertDocumentAttachment } from './yjs' ; export const STORED_POSITION_PREFIX = '__slateYjsStoredPosition_' ; export function slatePointToRelativePosition ( sharedRoot , slateRoot , point ) { const { yTarget , yParent , textRange } = getYTarget ( sharedRoot , slateRoot , point . path ) ; if ( yTarget ) { throw new Error ( 'Slate point points to a non-text element inside sharedRoot' ) ; } return Y . createRelativePositionFromTypeIndex ( yParent , textRange . start + point . offset , point . offset === textRange . end ? - 1 : 0 ) ; } export function absolutePositionToSlatePoint ( sharedRoot , slateRoot , { type , index , assoc } ) { if ( ! ( type instanceof Y . XmlText ) ) { throw new Error ( 'Absolute position points to a non-XMLText' ) ; } const parentPath = getSlatePath ( sharedRoot , slateRoot , type ) ; const parent = Node . get ( slateRoot , parentPath ) ; if ( Text . isText ( parent ) ) { throw new Error ( "Absolute position doesn't match slateRoot, cannot descent into text" ) ; } const [ pathOffset , textOffset ] = yOffsetToSlateOffsets ( parent , index , { assoc , } ) ; const target = parent . children [ pathOffset ] ; if ( ! Text . isText ( target ) ) { return null ; } return { path : [ ... parentPath , pathOffset ] , offset : textOffset } ; } export function relativePositionToSlatePoint ( sharedRoot , slateRoot , pos ) { if ( ! sharedRoot . doc ) { throw new Error ( "sharedRoot isn't attach to a yDoc" ) ; } const absPos = Y . createAbsolutePositionFromRelativePosition ( pos , sharedRoot . doc ) ; return absPos && absolutePositionToSlatePoint ( sharedRoot , slateRoot , absPos ) ; } export function getStoredPosition ( sharedRoot , key ) { const rawPosition = sharedRoot . getAttribute ( STORED_POSITION_PREFIX + key ) ; if ( ! rawPosition ) { return null ; } return Y . decodeRelativePosition ( rawPosition ) ; } export function getStoredPositions ( sharedRoot ) { return Object . fromEntries ( Object . entries ( sharedRoot . getAttributes ( ) ) . filter ( ( [ key ] ) => key . startsWith ( STORED_POSITION_PREFIX ) ) . map ( ( [ key , position ] ) => [ key . slice ( STORED_POSITION_PREFIX . length ) , Y . createRelativePositionFromJSON ( position ) , ] ) ) ; } function getStoredPositionsAbsolute ( sharedRoot ) { assertDocumentAttachment ( sharedRoot ) ; return Object . fromEntries ( Object . entries ( sharedRoot . getAttributes ( ) ) . filter ( ( [ key ] ) => key . startsWith ( STORED_POSITION_PREFIX ) ) . map ( ( [ key , position ] ) => [ key . slice ( STORED_POSITION_PREFIX . length ) , Y . createAbsolutePositionFromRelativePosition ( Y . createRelativePositionFromJSON ( position ) , sharedRoot . doc ) , ] as const ) . filter ( ( [ , position ] ) => position ) ) as Record < string , Y . AbsolutePosition > ; } export function removeStoredPosition ( sharedRoot , key ) { sharedRoot . removeAttribute ( STORED_POSITION_PREFIX + key ) ; } export function setStoredPosition ( sharedRoot , key , position ) { sharedRoot . setAttribute ( STORED_POSITION_PREFIX + key , Y . encodeRelativePosition ( position ) ) ; } function getAbsolutePositionsInTextRange ( absolutePositions , yTarget , textRange ? ) { return Object . fromEntries ( Object . entries ( absolutePositions ) . filter ( ( [ , position ] ) => { if ( position . type !== yTarget ) { return false ; } if ( ! textRange ) { return true ; } return position . assoc >= 0 ? position . index >= textRange . start && position . index < textRange . end : position . index > textRange . start && position . index >= textRange . end ; } ) ) ; } function getAbsolutePositionsInYText ( absolutePositions , yText , parentPath = '' ) { const positions = { [ parentPath ] : getAbsolutePositionsInTextRange ( absolutePositions , yText ) , } ; const insertDelta = yTextToInsertDelta ( yText ) ; insertDelta . forEach ( ( { insert } , i ) => { if ( insert instanceof Y . XmlText ) { Object . assign ( positions , getAbsolutePositionsInYText ( absolutePositions , insert , parentPath ? `${ parentPath }.${ i }` : i . toString ( ) ) ) ; } } ) ; return positions ; } export function getStoredPositionsInDeltaAbsolute ( sharedRoot , yText , delta , deltaOffset = 0 ) { const absolutePositions = getStoredPositionsAbsolute ( sharedRoot ) ; const positions = { '' : getAbsolutePositionsInTextRange ( absolutePositions , yText , { start : deltaOffset , end : deltaOffset + getInsertDeltaLength ( delta ) , } ) , } ; delta . forEach ( ( { insert } , i ) => { if ( insert instanceof Y . XmlText ) { Object . assign ( positions , getAbsolutePositionsInYText ( absolutePositions , insert , i . toString ( ) ) ) ; } } ) ; return positions ; } export function restoreStoredPositionsWithDeltaAbsolute ( sharedRoot , yText , absolutePositions , delta , newDeltaOffset = 0 , previousDeltaOffset = 0 , path = '' ) { const toRestore = absolutePositions [ path ] ; if ( toRestore ) { Object . entries ( toRestore ) . forEach ( ( [ key , position ] ) => { setStoredPosition ( sharedRoot , key , Y . createRelativePositionFromTypeIndex ( yText , position . index - previousDeltaOffset + newDeltaOffset , position . assoc ) ) ; } ) ; } delta . forEach ( ( { insert } , i ) => { if ( insert instanceof Y . XmlText ) { restoreStoredPositionsWithDeltaAbsolute ( sharedRoot , insert , absolutePositions , yTextToInsertDelta ( insert ) , 0 , 0 , path ? `${ path }.${ i }` : i . toString ( ) ) ; } } ) ; } export function slateRangeToRelativeRange ( sharedRoot , slateRoot , range ) { return { anchor : slatePointToRelativePosition ( sharedRoot , slateRoot , range . anchor ) , focus : slatePointToRelativePosition ( sharedRoot , slateRoot , range . focus ) , } ; } export function relativeRangeToSlateRange ( sharedRoot , slateRoot , range ) { const anchor = relativePositionToSlatePoint ( sharedRoot , slateRoot , range . anchor ) ; if ( ! anchor ) { return null ; } const focus = relativePositionToSlatePoint ( sharedRoot , slateRoot , range . focus ) ; if ( ! focus ) { return null ; } return { anchor , focus } ; }