All files / roosterjs-content-model-dom/lib/modelToDom/handlers handleParagraph.ts

100% Statements 50/50
96.97% Branches 64/66
100% Functions 7/7
100% Lines 48/48

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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 1331x 1x 1x 1x 1x 1x             1x         1x             993x   993x 40x   953x   953x     36x 953x             953x   953x   953x         953x 953x   953x 953x   953x       101x                         953x 1169x 1169x   1169x 1025x           953x 633x   633x 633x 633x             633x   320x     953x               953x   953x 953x 953x     953x 633x 519x     633x   320x 320x         993x    
import { applyFormat } from '../utils/applyFormat';
import { getObjectKeys } from '../../domUtils/getObjectKeys';
import { optimize } from '../optimizers/optimize';
import { reuseCachedElement } from '../../domUtils/reuseCachedElement';
import { stackFormat } from '../utils/stackFormat';
import { unwrap } from '../../domUtils/unwrap';
import type {
    ContentModelBlockHandler,
    ContentModelParagraph,
    ModelToDomContext,
} from 'roosterjs-content-model-types';
 
const DefaultParagraphTag = 'div';
 
/**
 * @internal
 */
export const handleParagraph: ContentModelBlockHandler<ContentModelParagraph> = (
    doc: Document,
    parent: Node,
    paragraph: ContentModelParagraph,
    context: ModelToDomContext,
    refNode: Node | null
) => {
    let container = context.allowCacheElement ? paragraph.cachedElement : undefined;
 
    if (container && paragraph.segments.every(x => x.segmentType != 'General' && !x.isSelected)) {
        refNode = reuseCachedElement(parent, container, refNode, context.rewriteFromModel);
    } else {
        stackFormat(context, paragraph.decorator?.tagName || null, () => {
            const needParagraphWrapper =
                !paragraph.isImplicit ||
                !!paragraph.decorator ||
                (getObjectKeys(paragraph.format).length > 0 &&
                    paragraph.segments.some(segment => segment.segmentType != 'SelectionMarker'));
            const formatOnWrapper = needParagraphWrapper
                ? {
                      ...(paragraph.decorator?.format || {}),
                      ...paragraph.segmentFormat,
                  }
                : {};
 
            container = doc.createElement(paragraph.decorator?.tagName || DefaultParagraphTag);
 
            parent.insertBefore(container, refNode);
 
            context.regularSelection.current = {
                block: needParagraphWrapper ? container : container.parentNode,
                segment: null,
            };
 
            const handleSegments = () => {
                const parent = container;
 
                Eif (parent) {
                    const firstSegment = paragraph.segments[0];
 
                    if (firstSegment?.segmentType == 'SelectionMarker') {
                        // Make sure there is a segment created before selection marker.
                        // If selection marker is the first selected segment in a paragraph, create a dummy text node,
                        // so after rewrite, the regularSelection object can have a valid segment object set to the text node.
                        context.modelHandlers.text(
                            doc,
                            parent,
                            {
                                ...firstSegment,
                                segmentType: 'Text',
                                text: '',
                            },
                            context,
                            []
                        );
                    }
 
                    paragraph.segments.forEach(segment => {
                        const newSegments: Node[] = [];
                        context.modelHandlers.segment(doc, parent, segment, context, newSegments);
 
                        newSegments.forEach(node => {
                            context.domIndexer?.onSegment(node, paragraph, [segment]);
                        });
                    });
                }
            };
 
            if (needParagraphWrapper) {
                stackFormat(context, formatOnWrapper, handleSegments);
 
                applyFormat(container, context.formatAppliers.block, paragraph.format, context);
                applyFormat(container, context.formatAppliers.container, paragraph.format, context);
                applyFormat(
                    container,
                    context.formatAppliers.segmentOnBlock,
                    formatOnWrapper,
                    context
                );
 
                context.paragraphMap?.applyMarkerToDom(container, paragraph);
            } else {
                handleSegments();
            }
 
            optimize(container, context);
 
            // It is possible the next sibling node is changed during processing child segments
            // e.g. When this paragraph is an implicit paragraph and it contains an inline entity segment
            // The segment will be appended to container as child then the container will be removed
            // since this paragraph it is implicit. In that case container.nextSibling will become original
            // inline entity's next sibling. So reset refNode to its real next sibling (after change) here
            // to make sure the value is correct.
            refNode = container.nextSibling;
 
            Eif (container) {
                context.onNodeCreated?.(paragraph, container);
                context.domIndexer?.onParagraph(container);
            }
 
            if (needParagraphWrapper) {
                if (context.allowCacheElement) {
                    paragraph.cachedElement = container;
                }
 
                context.rewriteFromModel.addedBlockElements.push(container);
            } else {
                unwrap(container);
                container = undefined;
            }
        });
    }
 
    return refNode;
};