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

98.08% Statements 51/52
96.15% Branches 25/26
100% Functions 7/7
97.83% Lines 45/46

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 1071x               1x 1x 1x         1x     18x 18x 18x       18x 60x 60x 48x   12x     18x           1x 31x 31x         12x   12x 3x 3x   3x               1x 16x 8x   8x 6x 6x 2x   4x         1x 8x 8x   8x   16x         8x 1x     7x 13x 13x 6x 6x   7x           7x 7x          
import { mutateSegment } from 'roosterjs-content-model-dom';
import type {
    ContentModelTable,
    InsertPoint,
    ShallowMutableContentModelListItem,
    ShallowMutableContentModelParagraph,
} from 'roosterjs-content-model-types';
 
const EN_SPACE = '\u2002';
const REGULAR_SPACE = '\u0020';
const NON_BREAK_SPACES = '\u00A0';
 
/**
 * @internal
 */
export const IndentStepInPixel = 40;
 
function countTabsSpaces(text: string) {
    const spaces = countSpacesBeforeText(text);
    const tabSpaces = Math.floor(spaces / 4);
    return tabSpaces;
}
 
function countSpacesBeforeText(str: string) {
    let count = 0;
    for (const char of str) {
        if (char === EN_SPACE || char === NON_BREAK_SPACES || char == REGULAR_SPACE) {
            count++;
        } else {
            break;
        }
    }
    return count;
}
 
/**
 * @internal
 */
export function adjustListIndentation(listItem: ShallowMutableContentModelListItem) {
    const block = listItem.blocks[0];
    if (
        block.blockType == 'Paragraph' &&
        block.segments.length > 0 &&
        block.segments[0].segmentType == 'Text'
    ) {
        const tabSpaces = countTabsSpaces(block.segments[0].text);
 
        if (tabSpaces > 0) {
            mutateSegment(block, block.segments[0], textSegment => {
                textSegment.text = textSegment.text.substring(tabSpaces * 4);
            });
            listItem.levels[0].format.marginLeft = tabSpaces * IndentStepInPixel + 'px';
        }
    }
}
 
/**
 * @internal
 */
export function adjustTableIndentation(insertPoint: InsertPoint, table: ContentModelTable) {
    const { paragraph, marker } = insertPoint;
    const indentationMargin = getTableIndentation(paragraph);
 
    if (indentationMargin) {
        insertPoint.paragraph.segments = [marker];
        if (insertPoint.paragraph.format.direction == 'rtl') {
            table.format.marginRight = indentationMargin * IndentStepInPixel + 'px';
        } else {
            table.format.marginLeft = indentationMargin * IndentStepInPixel + 'px';
        }
    }
}
 
const getTableIndentation = (paragraph: ShallowMutableContentModelParagraph) => {
    let tabsNumber = 0;
    const segments = paragraph.segments;
 
    const isEmptyLine = paragraph.segments.every(
        s =>
            (s.segmentType == 'Text' && s.text.trim().length == 0) ||
            s.segmentType == 'SelectionMarker' ||
            s.segmentType == 'Br'
    );
 
    if (!isEmptyLine) {
        return;
    }
 
    let numberOfSegments = 0;
    for (const seg of segments) {
        if (seg.segmentType === 'Text') {
            tabsNumber = tabsNumber + countTabsSpaces(seg.text);
            numberOfSegments++;
        } else {
            break;
        }
    }
 
    // Text segments must be >= (total segments - 2) to apply margin.
    // If not, the selection marker is likely between  texts segment, so skip margin adjustment.
    Eif (segments.length - 2 <= numberOfSegments) {
        return tabsNumber;
    }
 
    return;
};