All files / roosterjs-content-model-api/lib/modelApi/common reducedModelChildProcessor.ts

74.19% Statements 23/31
78.26% Branches 18/23
66.67% Functions 2/3
72.41% Lines 21/29

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 931x                                                     1x         28x 10x 10x     28x   28x 10x       10x   10x       10x   10x             18x         8x 8x   8x 14x     1x   13x     14x     8x                          
import {
    getRegularSelectionOffsets,
    getSelectionRootNode,
    handleRegularSelection,
    isNodeOfType,
    processChildNode,
} from 'roosterjs-content-model-dom';
import type { ContentModelBlockGroup, DomToModelContext } from 'roosterjs-content-model-types';
 
/**
 * @internal
 */
interface FormatStateContext extends DomToModelContext {
    /**
     * An optional stack of parent elements to process. When provided, the child nodes of current parent element will be ignored,
     * but use the top element in this stack instead in childProcessor.
     */
    nodeStack?: Node[];
}
 
/**
 * @internal
 * In order to get format, we can still use the regular child processor. However, to improve performance, we don't need to create
 * content model for the whole doc, instead we only need to traverse the tree path that can arrive current selected node.
 * This "reduced" child processor will first create a node stack that stores DOM node from root to current common ancestor node of selection,
 * then use this stack as a faked DOM tree to create a reduced content model which we can use to retrieve format state
 */
export function reducedModelChildProcessor(
    group: ContentModelBlockGroup,
    parent: ParentNode,
    context: FormatStateContext
) {
    if (!context.nodeStack) {
        const selectionRootNode = getSelectionRootNode(context.selection);
        context.nodeStack = selectionRootNode ? createNodeStack(parent, selectionRootNode) : [];
    }
 
    const stackChild = context.nodeStack.pop();
 
    if (stackChild) {
        const [nodeStartOffset, nodeEndOffset] = getRegularSelectionOffsets(context, parent);
 
        // If selection is not on this node, skip getting node index to save some time since we don't need it here
        const index =
            nodeStartOffset >= 0 || nodeEndOffset >= 0 ? getChildIndex(parent, stackChild) : -1;
 
        Iif (index >= 0) {
            handleRegularSelection(index, context, group, nodeStartOffset, nodeEndOffset);
        }
 
        processChildNode(group, stackChild, context);
 
        Iif (index >= 0) {
            handleRegularSelection(index + 1, context, group, nodeStartOffset, nodeEndOffset);
        }
    } else {
        // No child node from node stack, that means we have reached the deepest node of selection.
        // Now we can use default child processor to perform full sub tree scanning for content model,
        // So that all selected node will be included.
        context.defaultElementProcessors.child(group, parent, context);
    }
}
 
function createNodeStack(root: Node, startNode: Node): Node[] {
    const result: Node[] = [];
    let node: Node | null = startNode;
 
    while (node && root != node && root.contains(node)) {
        if (isNodeOfType(node, 'ELEMENT_NODE') && node.tagName == 'TABLE') {
            // For table, we can't do a reduced model creation since we need to handle their cells and indexes,
            // so clean up whatever we already have, and just put table into the stack
            result.splice(0, result.length, node);
        } else {
            result.push(node);
        }
 
        node = node.parentNode;
    }
 
    return result;
}
 
function getChildIndex(parent: ParentNode, stackChild: Node) {
    let index = 0;
    let child = parent.firstChild;
 
    while (child && child != stackChild) {
        index++;
        child = child.nextSibling;
    }
    return index;
}