import { Descendant , Editor , Element , Node , NodeEntry , Operation , Path , PathRef , PointRef , Range , RangeRef , Text , Transforms , } from './' ; import { DIRTY_PATHS , DIRTY_PATH_KEYS , FLUSHING } from './utils/weak-maps' ; /**
 * Create a new Slate `Editor` object.
 */ export const createEditor = ( ) : Editor => { const editor = { children : [ ] , operations : [ ] , selection : null , marks : null , isInline : ( ) => false , isVoid : ( ) => false , onChange : ( ) => { } , apply : ( op ) => { for ( const ref of Editor . pathRefs ( editor ) ) { PathRef . transform ( ref , op ) ; } for ( const ref of Editor . pointRefs ( editor ) ) { PointRef . transform ( ref , op ) ; } for ( const ref of Editor . rangeRefs ( editor ) ) { RangeRef . transform ( ref , op ) ; } const oldDirtyPaths = DIRTY_PATHS . get ( editor ) || [ ] ; const oldDirtyPathKeys = DIRTY_PATH_KEYS . get ( editor ) || new Set ( ) ; let dirtyPaths ; let dirtyPathKeys ; const add = ( path ) => { if ( path ) { const key = path . join ( ',' ) ; if ( ! dirtyPathKeys . has ( key ) ) { dirtyPathKeys . add ( key ) ; dirtyPaths . push ( path ) ; } } } ; if ( Path . operationCanTransformPath ( op ) ) { dirtyPaths = [ ] ; dirtyPathKeys = new Set ( ) ; for ( const path of oldDirtyPaths ) { const newPath = Path . transform ( path , op ) ; add ( newPath ) ; } } else { dirtyPaths = oldDirtyPaths ; dirtyPathKeys = oldDirtyPathKeys ; } const newDirtyPaths = getDirtyPaths ( op ) ; for ( const path of newDirtyPaths ) { add ( path ) ; } DIRTY_PATHS . set ( editor , dirtyPaths ) ; DIRTY_PATH_KEYS . set ( editor , dirtyPathKeys ) ; Transforms . transform ( editor , op ) ; editor . operations . push ( op ) ; Editor . normalize ( editor ) ; if ( op . type === 'set_selection' ) { editor . marks = null ; } if ( ! FLUSHING . get ( editor ) ) { FLUSHING . set ( editor , true ) ; Promise . resolve ( ) . then ( ( ) => { FLUSHING . set ( editor , false ) ; editor . onChange ( ) ; editor . operations = [ ] ; } ) ; } } , addMark : ( key , value ) => { const { selection } = editor ; if ( selection ) { if ( Range . isExpanded ( selection ) ) { Transforms . setNodes ( editor , { [ key ] : value } , { match : Text . isText , split : true } ) ; } else { const marks = { ... ( Editor . marks ( editor ) || { } ) , [ key ] : value , } ; editor . marks = marks ; if ( ! FLUSHING . get ( editor ) ) { editor . onChange ( ) ; } } } } , deleteBackward : ( unit ) => { const { selection } = editor ; if ( selection && Range . isCollapsed ( selection ) ) { Transforms . delete ( editor , { unit , reverse : true } ) ; } } , deleteForward : ( unit ) => { const { selection } = editor ; if ( selection && Range . isCollapsed ( selection ) ) { Transforms . delete ( editor , { unit } ) ; } } , deleteFragment : ( direction ? ) => { const { selection } = editor ; if ( selection && Range . isExpanded ( selection ) ) { Transforms . delete ( editor , { reverse : direction === 'backward' } ) ; } } , getFragment : ( ) => { const { selection } = editor ; if ( selection ) { return Node . fragment ( editor , selection ) ; } return [ ] ; } , insertBreak : ( ) => { Transforms . splitNodes ( editor , { always : true } ) ; } , insertFragment : ( fragment ) => { Transforms . insertFragment ( editor , fragment ) ; } , insertNode : ( node ) => { Transforms . insertNodes ( editor , node ) ; } , insertText : ( text ) => { const { selection , marks } = editor ; if ( selection ) { if ( marks ) { const node = { text , ... marks } ; Transforms . insertNodes ( editor , node ) ; } else { Transforms . insertText ( editor , text ) ; } editor . marks = null ; } } , normalizeNode : ( entry ) => { const [ node , path ] = entry ; if ( Text . isText ( node ) ) { return ; } if ( Element . isElement ( node ) && node . children . length === 0 ) { const child = { text : '' } ; Transforms . insertNodes ( editor , child , { at : path . concat ( 0 ) , voids : true , } ) ; return ; } const shouldHaveInlines = Editor . isEditor ( node ) ? false : Element . isElement ( node ) && ( editor . isInline ( node ) || node . children . length === 0 || Text . isText ( node . children [ 0 ] ) || editor . isInline ( node . children [ 0 ] ) ) ; let n = 0 ; for ( let i = 0 ; i < node . children . length ; i ++ , n ++ ) { const currentNode = Node . get ( editor , path ) ; if ( Text . isText ( currentNode ) ) continue ; const child = node . children [ i ] as Descendant ; const prev = currentNode . children [ n - 1 ] as Descendant ; const isLast = i === node . children . length - 1 ; const isInlineOrText = Text . isText ( child ) || ( Element . isElement ( child ) && editor . isInline ( child ) ) ; if ( isInlineOrText !== shouldHaveInlines ) { Transforms . removeNodes ( editor , { at : path . concat ( n ) , voids : true } ) ; n -- ; } else if ( Element . isElement ( child ) ) { if ( editor . isInline ( child ) ) { if ( prev == null || ! Text . isText ( prev ) ) { const newChild = { text : '' } ; Transforms . insertNodes ( editor , newChild , { at : path . concat ( n ) , voids : true , } ) ; n ++ ; } else if ( isLast ) { const newChild = { text : '' } ; Transforms . insertNodes ( editor , newChild , { at : path . concat ( n + 1 ) , voids : true , } ) ; n ++ ; } } } else { if ( prev != null && Text . isText ( prev ) ) { if ( Text . equals ( child , prev , { loose : true } ) ) { Transforms . mergeNodes ( editor , { at : path . concat ( n ) , voids : true } ) ; n -- ; } else if ( prev . text === '' ) { Transforms . removeNodes ( editor , { at : path . concat ( n - 1 ) , voids : true , } ) ; n -- ; } else if ( child . text === '' ) { Transforms . removeNodes ( editor , { at : path . concat ( n ) , voids : true , } ) ; n -- ; } } } } } , removeMark : ( key ) => { const { selection } = editor ; if ( selection ) { if ( Range . isExpanded ( selection ) ) { Transforms . unsetNodes ( editor , key , { match : Text . isText , split : true , } ) ; } else { const marks = { ... ( Editor . marks ( editor ) || { } ) } ; delete marks [ key ] ; editor . marks = marks ; if ( ! FLUSHING . get ( editor ) ) { editor . onChange ( ) ; } } } } , } ; return editor ; } ; /**
 * Get the "dirty" paths generated from an operation.
 */ const getDirtyPaths = ( op ) : Path [ ] => { switch ( op . type ) { case 'insert_text' : case 'remove_text' : case 'set_node' : { const { path } = op ; return Path . levels ( path ) ; } case 'insert_node' : { const { node , path } = op ; const levels = Path . levels ( path ) ; const descendants = Text . isText ( node ) ? [ ] : Array . from ( Node . nodes ( node ) , ( [ , p ] ) => path . concat ( p ) ) ; return [ ... levels , ... descendants ] ; } case 'merge_node' : { const { path } = op ; const ancestors = Path . ancestors ( path ) ; const previousPath = Path . previous ( path ) ; return [ ... ancestors , previousPath ] ; } case 'move_node' : { const { path , newPath } = op ; if ( Path . equals ( path , newPath ) ) { return [ ] ; } const oldAncestors = [ ] ; const newAncestors = [ ] ; for ( const ancestor of Path . ancestors ( path ) ) { const p = Path . transform ( ancestor , op ) ; oldAncestors . push ( p ! ) ; } for ( const ancestor of Path . ancestors ( newPath ) ) { const p = Path . transform ( ancestor , op ) ; newAncestors . push ( p ! ) ; } const newParent = newAncestors [ newAncestors . length - 1 ] ; const newIndex = newPath [ newPath . length - 1 ] ; const resultPath = newParent . concat ( newIndex ) ; return [ ... oldAncestors , ... newAncestors , resultPath ] ; } case 'remove_node' : { const { path } = op ; const ancestors = Path . ancestors ( path ) ; return [ ... ancestors ] ; } case 'split_node' : { const { path } = op ; const levels = Path . levels ( path ) ; const nextPath = Path . next ( path ) ; return [ ... levels , nextPath ] ; } default : { return [ ] ; } } } ;