All files / roosterjs-content-model-api/lib/modelApi/common wrapBlock.ts

100% Statements 29/29
100% Branches 27/27
100% Functions 4/4
100% Lines 27/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 841x                                       1x             17x 17x 17x   17x 16x   16x 16x 16x   16x       16x 16x     16x             1x       18x 18x 18x 18x 18x   18x 5x 5x 5x                     11x   11x   11x    
import { addBlock, mutateBlock, setParagraphNotImplicit } from 'roosterjs-content-model-dom';
import type {
    ContentModelBlock,
    ContentModelBlockGroup,
    ReadonlyContentModelBlock,
    ReadonlyContentModelBlockGroup,
    ShallowMutableContentModelBlock,
} from 'roosterjs-content-model-types';
 
/**
 * @internal
 */
export interface WrapBlockStep1Result<T extends ContentModelBlockGroup & ContentModelBlock> {
    parent: ReadonlyContentModelBlockGroup;
    wrapper: T;
}
 
/**
 * @internal
 */
export function wrapBlockStep1<T extends ContentModelBlockGroup & ContentModelBlock>(
    step1Result: WrapBlockStep1Result<T>[],
    readonlyParent: ReadonlyContentModelBlockGroup | null,
    readonlyBlockToWrap: ReadonlyContentModelBlock,
    creator: (isRtl: boolean) => T,
    canMerge: (isRtl: boolean, target: ShallowMutableContentModelBlock) => target is T
) {
    const parent = readonlyParent ? mutateBlock(readonlyParent) : null;
    const blockToWrap = mutateBlock(readonlyBlockToWrap);
    const index = parent?.blocks.indexOf(blockToWrap) ?? -1;
 
    if (parent && index >= 0) {
        parent.blocks.splice(index, 1);
 
        const readonlyPrevBlock: ReadonlyContentModelBlock = parent.blocks[index - 1];
        const prevBlock = readonlyPrevBlock ? mutateBlock(readonlyPrevBlock) : null;
        const isRtl = blockToWrap.format.direction == 'rtl';
        const wrapper =
            prevBlock && canMerge(isRtl, prevBlock)
                ? prevBlock
                : createAndAdd(parent, index, creator, isRtl);
 
        setParagraphNotImplicit(blockToWrap);
        addBlock(wrapper, blockToWrap);
 
        // Use reverse order, so that we can merge from last to first to avoid modifying unmerged quotes
        step1Result.unshift({ parent, wrapper });
    }
}
 
/**
 * @internal
 */
export function wrapBlockStep2<T extends ContentModelBlockGroup & ContentModelBlock>(
    step1Result: WrapBlockStep1Result<T>[],
    canMerge: (isRtl: boolean, target: ShallowMutableContentModelBlock, current: T) => target is T
) {
    step1Result.forEach(({ parent, wrapper }) => {
        const index = parent.blocks.indexOf(wrapper);
        const readonlyNextBlock = parent.blocks[index + 1];
        const nextBlock = readonlyNextBlock ? mutateBlock(readonlyNextBlock) : null;
        const isRtl = wrapper.format.direction == 'rtl';
 
        if (index >= 0 && nextBlock && canMerge(isRtl, nextBlock, wrapper)) {
            wrapper.blocks.forEach(setParagraphNotImplicit);
            wrapper.blocks.push(...nextBlock.blocks);
            mutateBlock(parent).blocks.splice(index + 1, 1);
        }
    });
}
 
function createAndAdd<T extends ContentModelBlockGroup & ContentModelBlock>(
    parent: ReadonlyContentModelBlockGroup,
    index: number,
    creator: (isRtl: boolean) => T,
    isRtl: boolean
): T {
    const block = creator(isRtl);
 
    mutateBlock(parent).blocks.splice(index, 0, block);
 
    return block;
}