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       1608x 4x     1604x 1604x   1604x 927x     1604x           1604x     1604x   1604x         927x 241x 686x 647x   39x   39x 39x   39x 39x 39x     39x                     78x 24x 20x 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;
        }
    }
}