All files / roosterjs-content-model-plugins/lib/findReplace/utils replaceTextInRange.ts

100% Statements 33/33
83.33% Branches 40/48
100% Functions 3/3
100% Lines 28/28

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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 1011x         1x         5x       1x     4x 4x 4x   4x   4x                         4x   4x 8x   8x   3x       3x       3x           5x 1x                 4x 4x   4x 4x   4x     4x   4x                   8x               8x 8x      
import { getObjectKeys, isNodeOfType } from 'roosterjs-content-model-dom';
 
/**
 * @internal
 */
export function replaceTextInRange(
    range: Range,
    replaceText: string,
    foundRanges: Range[]
): Range | null {
    if (
        !isNodeOfType(range.startContainer, 'TEXT_NODE') ||
        !isNodeOfType(range.endContainer, 'TEXT_NODE')
    ) {
        return null;
    }
 
    const textNode = range.startContainer;
    const resultContainer = range.startContainer;
    const resultOffset = range.startOffset + replaceText.length;
 
    const originalText = textNode.textContent || '';
    const newText =
        originalText.substring(0, range.startOffset) +
        replaceText +
        (range.endContainer == range.startContainer
            ? originalText.substring(range.endOffset, textNode.textContent?.length)
            : '');
    const pendingRanges: Record<
        number,
        {
            startContainer: Node;
            startOffset: number;
            endContainer: Node;
            endOffset: number;
        }
    > = {};
 
    for (let i = 0; i < foundRanges.length; i++) {
        const r = foundRanges[i];
 
        if (r.startContainer == range.endContainer && r.startOffset >= range.endOffset) {
            const startOffset =
                range.startContainer == range.endContainer
                    ? r.startOffset - range.endOffset + range.startOffset + replaceText.length
                    : r.startOffset - range.endOffset;
            const endOffset =
                r.startContainer == r.endContainer
                    ? startOffset + (r.endOffset - r.startOffset)
                    : r.endOffset;
 
            pendingRanges[i] = {
                startContainer: r.startContainer,
                endContainer: r.endContainer,
                startOffset,
                endOffset,
            };
        } else if (r.endContainer == range.startContainer && r.endOffset <= range.startOffset) {
            pendingRanges[i] = {
                startContainer: r.startContainer,
                endContainer: r.endContainer,
                startOffset: r.startOffset,
                endOffset: r.endOffset,
            };
        }
    }
 
    range.deleteContents();
    textNode.nodeValue = newText;
 
    getObjectKeys(pendingRanges).forEach(i => {
        const { startOffset, endOffset, startContainer, endContainer } = pendingRanges[i];
 
        safeSetRange(foundRanges[i], startContainer, startOffset, endContainer, endOffset);
    });
 
    safeSetRange(range, resultContainer, resultOffset, resultContainer, resultOffset);
 
    return range;
}
 
function safeSetRange(
    range: Range,
    startContainer: Node,
    startOffset: number,
    endContainer: Node,
    endOffset: number
) {
    Eif (
        isNodeOfType(startContainer, 'TEXT_NODE') &&
        isNodeOfType(endContainer, 'TEXT_NODE') &&
        startOffset >= 0 &&
        startOffset <= (startContainer.nodeValue?.length ?? 0) &&
        endOffset >= 0 &&
        endOffset <= (endContainer.nodeValue?.length ?? 0)
    ) {
        range.setStart(startContainer, startOffset);
        range.setEnd(endContainer, endOffset);
    }
}