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       3152x 4x     3148x 3148x   3148x 1964x     3148x           3148x     3148x   3148x         1964x 690x 1274x 1203x   71x   71x 71x   71x 71x 71x     71x                     142x 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;
        }
    }
}