import Delta from 'quill-delta' ; import { getTextAttributes , Attributes } from './attributes' ; import { Selection , SelectionShape } from './Selection' ; import { GenericOp } from './operations' ; import mergeRight from 'ramda/es/mergeRight' ; import pickBy from 'ramda/es/pickBy' ; import head from 'ramda/es/head' ; import { GenericRichContent , extractTextFromDelta } from './generic' ; import { DeltaDiffComputer } from './DeltaDiffComputer' ; import { DeltaChangeContext } from './DeltaChangeContext' ; import { DocumentLine , LineWalker } from './LineWalker' ; import { DocumentDeltaAtomicUpdate } from './DocumentDeltaAtomicUpdate' ; export class DocumentDelta implements GenericRichContent { public get ops ( ) { return this . delta . ops ; } private delta : Delta ; private text : string | null = null ; public constructor ( arg ? ) { this . delta = arg instanceof DocumentDelta ? new Delta ( arg . delta ) : new Delta ( arg ) ; } private getText ( ) { if ( this . text !== null ) { return this . text ; } this . text = extractTextFromDelta ( this . delta ) ; return this . text ; } returns param selection private getSelectionEncompassingLines ( selection ) { let accumulatedLength = 0 ; let newSelectionStart = selection . start ; let newSelectionEnd = selection . end ; let isUpperBoundFrozen = false ; this . delta . eachLine ( l => { if ( selection . start > accumulatedLength - 1 ) { newSelectionStart = accumulatedLength ; } accumulatedLength += l . length ( ) + 1 ; if ( selection . end < accumulatedLength && ! isUpperBoundFrozen ) { newSelectionEnd = accumulatedLength ; isUpperBoundFrozen = true ; } } ) ; return Selection . fromBounds ( newSelectionStart , newSelectionEnd ) ; } param selection returns public getSelected ( selection ) { return this . create ( this . delta . slice ( selection . start , selection . end ) ) ; } public compose ( delta ) { return this . create ( this . delta . compose ( delta ) ) ; } public create ( delta ) { return new DocumentDelta ( delta ) ; } public length ( ) { return this . delta . length ( ) ; } public concat ( delta ) { return this . create ( this . delta . concat ( delta instanceof DocumentDelta ? delta . delta : delta ) ) ; } public eachLine ( predicate ) { new LineWalker ( this . delta ) . eachLine ( predicate ) ; } remarks param newText param deltaChangeContext param cursorTextAttributes returns public applyTextDiff ( newText , deltaChangeContext , cursorTextAttributes = { } ) { const oldText = this . getText ( ) ; const computer = new DeltaDiffComputer ( { cursorTextAttributes , newText , oldText , context : deltaChangeContext , } , this ) ; const { delta } = computer . toDeltaDiffReport ( ) ; return new DocumentDeltaAtomicUpdate ( this . compose ( delta ) , delta , deltaChangeContext . selectionAfterChange ) ; } remarks param selection returns public getLineTypeInSelection ( selection ) { const selected = this . getSelected ( this . getSelectionEncompassingLines ( selection ) ) ; const lineAttributes = [ ] ; selected . delta . eachLine ( ( l , a ) => { lineAttributes . push ( a ) ; } ) ; const firstAttr = head < Attributes . Map > ( lineAttributes ) ; let type = 'normal' ; if ( firstAttr ) { if ( firstAttr . $type == null ) { return 'normal' ; } type = firstAttr . $type as Attributes . LineType ; } const isType = lineAttributes . every ( v => v . $type === type ) ; if ( isType ) { return type ; } return 'normal' ; } remarks param selection returns public getSelectedTextAttributes ( selection ) { let realSelection = Selection . fromShape ( selection ) ; if ( selection . start === selection . end ) { if ( selection . start === 0 ) { return { } ; } realSelection = Selection . fromBounds ( selection . start - 1 , selection . end ) ; } const deltaSelection = this . getSelected ( realSelection ) ; const attributesList = deltaSelection . delta . filter ( op => typeof op . insert === 'string' && op . insert !== '\n' ) . map ( op => op . attributes || { } ) ; const attributes = attributesList . reduce ( mergeRight , { } ) ; const realAttributes = pickBy ( ( value , attributeName ) => attributesList . every ( localValue => localValue [ attributeName ] === value ) ) ( attributes ) ; return getTextAttributes ( realAttributes ) ; } param selectionBeforeChange param attributeName param attributeValue public applyTextTransformToSelection ( selectionBeforeChange , attributeName , attributeValue ) { const allOperationsMatchAttributeValue = this . getSelected ( selectionBeforeChange ) . ops . every ( op => ! ! op . attributes && op . attributes [ attributeName ] === attributeValue ) ; if ( allOperationsMatchAttributeValue ) { const clearAllDelta = new Delta ( ) ; clearAllDelta . retain ( selectionBeforeChange . start ) ; clearAllDelta . retain ( selectionBeforeChange . length ( ) , { [ attributeName ] : null } ) ; return new DocumentDeltaAtomicUpdate ( this . compose ( clearAllDelta ) , clearAllDelta , selectionBeforeChange ) ; } const replaceAllDelta = new Delta ( ) ; replaceAllDelta . retain ( selectionBeforeChange . start ) ; replaceAllDelta . retain ( selectionBeforeChange . length ( ) , { [ attributeName ] : attributeValue } ) ; return new DocumentDeltaAtomicUpdate ( this . compose ( replaceAllDelta ) , replaceAllDelta , selectionBeforeChange ) ; } }