All files / roosterjs-content-model-plugins/lib/edit/deleteSteps deleteEmptyQuote.ts

97.37% Statements 37/38
88.64% Branches 39/44
100% Functions 5/5
97.3% Lines 36/37

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 1041x                                   1x 29x   29x         58x 58x 29x 29x         29x   29x 6x 6x   6x 2x 2x 2x 4x         2x 2x 2x           1x 6x             1x 6x 10x       1x           2x   2x 2x   2x 2x         2x       2x     2x 2x   2x          
import {
    unwrapBlock,
    getClosestAncestorBlockGroupIndex,
    createFormatContainer,
    mutateBlock,
} from 'roosterjs-content-model-dom';
import type {
    DeleteSelectionStep,
    ReadonlyContentModelBlockGroup,
    ReadonlyContentModelFormatContainer,
    ReadonlyContentModelParagraph,
    ShallowMutableContentModelFormatContainer,
    ShallowMutableContentModelParagraph,
} from 'roosterjs-content-model-types';
 
/**
 * @internal
 */
export const deleteEmptyQuote: DeleteSelectionStep = context => {
    const { deleteResult } = context;
 
    Eif (
        deleteResult == 'nothingToDelete' ||
        deleteResult == 'notDeleted' ||
        deleteResult == 'range'
    ) {
        const { insertPoint, formatContext } = context;
        const { path, paragraph } = insertPoint;
        const rawEvent = formatContext?.rawEvent as KeyboardEvent;
        const index = getClosestAncestorBlockGroupIndex(
            path,
            ['FormatContainer'],
            ['TableCell', 'ListItem']
        );
        const quote = path[index];
 
        if (quote && quote.blockGroupType === 'FormatContainer' && quote.tagName == 'blockquote') {
            const parent = path[index + 1];
            const quoteBlockIndex = parent.blocks.indexOf(quote);
 
            if (isEmptyQuote(quote)) {
                unwrapBlock(parent, quote);
                rawEvent?.preventDefault();
                context.deleteResult = 'range';
            } else if (
                rawEvent?.key === 'Enter' &&
                quote.blocks.indexOf(paragraph) >= 0 &&
                isEmptyParagraph(paragraph)
            ) {
                insertNewLine(mutateBlock(quote), parent, quoteBlockIndex, paragraph);
                rawEvent?.preventDefault();
                context.deleteResult = 'range';
            }
        }
    }
};
 
const isEmptyQuote = (quote: ReadonlyContentModelFormatContainer) => {
    return (
        quote.blocks.length === 1 &&
        quote.blocks[0].blockType === 'Paragraph' &&
        isEmptyParagraph(quote.blocks[0])
    );
};
 
const isEmptyParagraph = (paragraph: ReadonlyContentModelParagraph) => {
    return paragraph.segments.every(
        s => s.segmentType === 'SelectionMarker' || s.segmentType === 'Br'
    );
};
 
const insertNewLine = (
    quote: ShallowMutableContentModelFormatContainer,
    parent: ReadonlyContentModelBlockGroup,
    quoteIndex: number,
    paragraph: ShallowMutableContentModelParagraph
) => {
    const paraIndex = quote.blocks.indexOf(paragraph);
 
    Eif (paraIndex >= 0) {
        const mutableParent = mutateBlock(parent);
 
        Eif (paraIndex < quote.blocks.length - 1) {
            const newQuote: ShallowMutableContentModelFormatContainer = createFormatContainer(
                quote.tagName,
                quote.format
            );
 
            newQuote.blocks.push(
                ...quote.blocks.splice(paraIndex + 1, quote.blocks.length - paraIndex - 1)
            );
 
            mutableParent.blocks.splice(quoteIndex + 1, 0, newQuote);
        }
 
        mutableParent.blocks.splice(quoteIndex + 1, 0, paragraph);
        quote.blocks.splice(paraIndex, 1);
 
        Iif (quote.blocks.length == 0) {
            mutableParent.blocks.splice(quoteIndex, 0);
        }
    }
};