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

100% Statements 38/38
100% Branches 16/16
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 1211x 1x 1x 1x 1x 1x 1x                             1x                             1x         55x           1x         13x     1x           68x 68x 68x   68x       68x     68x 68x         68x 272x     68x   68x 3x     68x     7x   7x 1x         7x 7x 7x   61x       68x       68x   68x              
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 (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
    );
}