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 34x 34x         12x   12x 3x 3x   3x               1x 34x 17x   17x 6x 6x 2x   4x         1x 17x 17x   17x   25x         17x 1x     16x 22x 22x 6x 6x   16x           16x 16x          
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;
};