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       2411x 4x     2407x 2407x   2407x 1404x     2407x           2407x     2407x   2407x         1404x 394x 1010x 973x   37x   37x 37x   37x 37x 37x     37x                     74x 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;
        }
    }
}