All files / roosterjs-content-model-dom/lib/modelToDom/optimizers optimize.ts

100% Statements 32/32
88.89% Branches 32/36
100% Functions 3/3
100% Lines 31/31

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 781x 1x 1x 1x                 1x       2291x 4x     2287x 2287x   2287x 1334x     2287x           2287x     2287x   2287x         1334x 377x 957x 925x   32x   32x 32x   32x 32x 32x     32x                     64x 26x 22x 4x 2x        
import { isEntityElement } from '../../domUtils/entityUtils';
import { isNodeOfType } from '../../domUtils/isNodeOfType';
import { mergeNode } from './mergeNode';
import { removeUnnecessarySpan } from './removeUnnecessarySpan';
import type {
    ModelToDomBlockAndSegmentNode,
    ModelToDomContext,
} from 'roosterjs-content-model-types';
 
/**
 * @internal
 */
export function optimize(root: Node, context: ModelToDomContext) {
    /**
     * Do no do any optimization to entity
     */
    if (isEntityElement(root)) {
        return;
    }
 
    removeUnnecessarySpan(root);
    mergeNode(root);
 
    for (let child = root.firstChild; child; child = child.nextSibling) {
        optimize(child, context);
    }
 
    normalizeTextNode(root, context);
}
 
// Merge continuous text nodes into one single node (same with normalize()),
// and update selection and dom indexes
function normalizeTextNode(root: Node, context: ModelToDomContext) {
    let lastText: Text | null = null;
    let child: Node | null;
    let next: Node | null;
    const selection = context.regularSelection;
 
    for (
        child = root.firstChild, next = child ? child.nextSibling : null;
        child;
        child = next, next = child ? child.nextSibling : null
    ) {
        if (!isNodeOfType(child, 'TEXT_NODE')) {
            lastText = null;
        } else if (!lastText) {
            lastText = child;
        } else {
            const originalLength = lastText.nodeValue?.length ?? 0;
 
            context.domIndexer?.onMergeText(lastText, child);
            lastText.nodeValue += child.nodeValue ?? '';
 
            Eif (selection) {
                updateSelection(selection.start, lastText, child, originalLength);
                updateSelection(selection.end, lastText, child, originalLength);
            }
 
            root.removeChild(child);
        }
    }
}
 
function updateSelection(
    mark: ModelToDomBlockAndSegmentNode | undefined,
    lastText: Text,
    nextText: Text,
    lastTextOriginalLength: number
) {
    if (mark && mark.offset == undefined) {
        if (mark.segment == lastText) {
            mark.offset = lastTextOriginalLength;
        } else if (mark.segment == nextText) {
            mark.segment = lastText;
        }
    }
}