import { FromToProps , sort } from '@remirror/core' ; import type { Annotation , OmitText } from './annotation-types' ; /**
 * Reflects one non-overlapping segment
 */ export interface Segment < Type extends Annotation > extends FromToProps { annotations : Array < OmitText < Type > > ; } interface Item < Type extends Annotation > { type : 'start' | 'end' ; annotation : OmitText < Type > ; } /**
 * Creates non-overlapping segments based on overlapping annotations.
 *
 * Prosemirror combines overlapping inline decorations by creating segments in
 * which the props of each overlapping decoration are merged.
 *
 * The Prosemirror approach doesn't allow to calculate styles based on multiple
 * annotations, e.g. a darker background shade if there are multiple
 * annotations.
 *
 * To overcome this limitations, this method calculates non-overlapping segments
 * with overlapping annotations. These segments can be used to create
 * ProseMirror decorations with a style that reflects all all annotations within
 * the segment.
 *
 * Approach was confirmed by Marijn:
 * https://discuss.prosemirror.net/t/how-to-style-overlapping-inline-decorations/3162
 */ export function toSegments < A extends Annotation > ( annotations ) { type AnnotationItem = Item < A > ; type AnnotationSegment = Segment < A > ; const segments = [ ] ; const positionMap = new Map ( ) ; for ( const annotation of annotations ) { const currentFrom = positionMap . get ( annotation . from ) ?? [ ] ; const currentTo = positionMap . get ( annotation . to ) ?? [ ] ; positionMap . set ( annotation . from , [ ... currentFrom , { type : 'start' , annotation } ] ) ; positionMap . set ( annotation . to , [ ... currentTo , { type : 'end' , annotation } ] ) ; } const sortedPositions = sort ( [ ... positionMap . entries ( ) ] , ( [ a ] , [ z ] ) => a - z ) ; let activeAnnotations = [ ] ; let from = 0 ; for ( const [ to , items ] of sortedPositions ) { const startAnnotations = items . filter ( ( item ) => item . type === 'start' ) . map ( ( item ) => item . annotation ) ; const endIds = new Set ( items . filter ( ( item ) => item . type === 'end' ) . map ( ( item ) => item . annotation . id ) ) ; if ( activeAnnotations . length > 0 ) { segments . push ( { from , to , annotations : activeAnnotations } ) ; } from = to ; activeAnnotations = [ ... activeAnnotations , ... startAnnotations ] . filter ( ( annotation ) => ! endIds . has ( annotation . id ) ) ; } return segments ; }