All files / roosterjs-content-model-dom/lib/domToModel/processors formatContainerProcessor.ts

100% Statements 38/38
100% Branches 17/17
100% Functions 6/6
100% Lines 36/36

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 1251x 1x 1x 1x 1x 1x 1x                             1x                             1x         74x           1x         13x     1x           87x 87x 87x   87x       87x     87x 87x         87x 348x     87x   87x 3x     87x             7x   7x 1x         7x 7x 7x   80x       87x       84x   84x              
import { addBlock } from '../../modelApi/common/addBlock';
import { createFormatContainer } from '../../modelApi/creators/createFormatContainer';
import { createParagraph } from '../../modelApi/creators/createParagraph';
import { getDefaultStyle } from '../utils/getDefaultStyle';
import { parseFormat } from '../utils/parseFormat';
import { setParagraphNotImplicit } from '../../modelApi/block/setParagraphNotImplicit';
import { stackFormat } from '../utils/stackFormat';
import type {
    ContentModelBlockGroup,
    ContentModelFormatContainer,
    ContentModelFormatContainerFormat,
    ContentModelParagraph,
    DomToModelContext,
    ElementProcessor,
    MarginFormat,
    PaddingFormat,
} from 'roosterjs-content-model-types';
 
/**
 * @internal
 */
export const ContextStyles: (keyof (MarginFormat & PaddingFormat))[] = [
    'marginLeft',
    'marginRight',
    'paddingLeft',
    'paddingRight',
];
 
/**
 * Content Model Element Processor for format container elements (e.g., blockquote, div)
 * Processes elements that create FormatContainer blocks in the content model.
 * This processor can be used in processorOverride to customize how specific elements are processed.
 * @param group The parent block group
 * @param element The DOM element to process
 * @param context DOM to Content Model context
 */
export const formatContainerProcessor: ElementProcessor<HTMLElement> = (
    group,
    element,
    context
) => {
    formatContainerProcessorInternal(group, element, context, false /* forceFormatContainer */);
};
 
/**
 * @internal
 */
export const forceFormatContainerProcessor: ElementProcessor<HTMLElement> = (
    group,
    element,
    context
) => {
    formatContainerProcessorInternal(group, element, context, true /* forceFormatContainer */);
};
 
const formatContainerProcessorInternal = (
    group: ContentModelBlockGroup,
    element: HTMLElement,
    context: DomToModelContext,
    forceFormatContainer: boolean
) => {
    stackFormat(context, { segment: 'shallowCloneForBlock', paragraph: 'shallowClone' }, () => {
        parseFormat(element, context.formatParsers.block, context.blockFormat, context);
        parseFormat(element, context.formatParsers.segmentOnBlock, context.segmentFormat, context);
 
        const format: ContentModelFormatContainerFormat = {
            ...context.blockFormat,
        };
 
        parseFormat(element, context.formatParsers.container, format, context);
 
        const tagName =
            getDefaultStyle(element).display == 'block' ? element.tagName.toLowerCase() : 'div';
        const formatContainer = createFormatContainer(tagName, format);
 
        // It is possible to inherit margin left/right styles from parent DIV or other containers,
        // since we are going into a deeper level of format container now,
        // the container will render these styles so no need to keep them in context format
        ContextStyles.forEach(style => {
            delete context.blockFormat[style];
        });
 
        context.elementProcessors.child(formatContainer, element, context);
 
        if (element.style.fontSize && parseInt(element.style.fontSize) == 0) {
            formatContainer.zeroFontSize = true;
        }
 
        if (
            !context.skipFormatContainerFallbackCheck &&
            shouldFallbackToParagraph(formatContainer) &&
            !forceFormatContainer
        ) {
            // For DIV container that only has one paragraph child, container style can be merged into paragraph
            // and no need to have this container
            const paragraph = formatContainer.blocks[0] as ContentModelParagraph;
 
            if (formatContainer.zeroFontSize) {
                paragraph.segmentFormat = Object.assign({}, paragraph.segmentFormat, {
                    fontSize: '0',
                });
            }
 
            Object.assign(paragraph.format, formatContainer.format);
            setParagraphNotImplicit(paragraph);
            addBlock(group, paragraph);
        } else {
            addBlock(group, formatContainer);
        }
    });
 
    addBlock(group, createParagraph(true /*isImplicit*/, context.blockFormat));
};
 
function shouldFallbackToParagraph(formatContainer: ContentModelFormatContainer) {
    const firstChild = formatContainer.blocks[0];
 
    return (
        formatContainer.tagName == 'div' &&
        formatContainer.blocks.length == 1 &&
        firstChild.blockType == 'Paragraph' &&
        firstChild.isImplicit
    );
}