All files / roosterjs-content-model-plugins/lib/autoFormat/numbers transformOrdinals.ts

100% Statements 33/33
93.33% Branches 28/30
100% Functions 3/3
100% Lines 33/33

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 821x               1x 24x         24x     1x         1x       1x         30x 30x 30x 30x 30x 7x 7x 7x 7x           5x     23x 23x 23x 12x       30x 17x             17x 17x     30x     53x 30x 30x 30x 24x 24x       6x    
import { splitTextSegment } from 'roosterjs-content-model-api';
 
import type {
    ContentModelText,
    FormatContentModelContext,
    ShallowMutableContentModelParagraph,
} from 'roosterjs-content-model-types';
 
const getOrdinal = (value: number) => {
    const ORDINALS: Record<number, string> = {
        1: 'st',
        2: 'nd',
        3: 'rd',
    };
    return ORDINALS[value] || 'th';
};
 
const ORDINALS = ['st', 'nd', 'rd', 'th'];
 
/**
 * The two last characters of ordinal number (st, nd, rd, th)
 */
const ORDINAL_LENGTH = 2;
 
/**
 * @internal
 */ export function transformOrdinals(
    previousSegment: ContentModelText,
    paragraph: ShallowMutableContentModelParagraph,
    context: FormatContentModelContext
): boolean {
    const value = previousSegment.text.split(' ').pop()?.trim();
    let shouldAddSuperScript = false;
    Eif (value) {
        const isOrdinal = ORDINALS.indexOf(value) > -1;
        if (isOrdinal) {
            const index = paragraph.segments.indexOf(previousSegment);
            const numberSegment = paragraph.segments[index - 1];
            let numericValue: number | null = null;
            if (
                numberSegment &&
                numberSegment.segmentType == 'Text' &&
                (numericValue = getNumericValue(numberSegment.text, true /* checkFullText */)) &&
                getOrdinal(numericValue) === value
            ) {
                shouldAddSuperScript = true;
            }
        } else {
            const ordinal = value.substring(value.length - ORDINAL_LENGTH); // This value  is equal st, nd, rd, th
            const numericValue = getNumericValue(value); //This is the numeric part. Ex: 10th, numeric value =
            if (numericValue && getOrdinal(numericValue) === ordinal) {
                shouldAddSuperScript = true;
            }
        }
 
        if (shouldAddSuperScript) {
            const ordinalSegment = splitTextSegment(
                previousSegment,
                paragraph,
                previousSegment.text.length - 3,
                previousSegment.text.length - 1
            );
 
            ordinalSegment.format.superOrSubScriptSequence = 'super';
            context.canUndoByBackspace = true;
        }
    }
    return shouldAddSuperScript;
}
 
function getNumericValue(text: string, checkFullText: boolean = false): number | null {
    const number = checkFullText ? text : text.substring(0, text.length - ORDINAL_LENGTH);
    const isNumber = /^-?\d+$/.test(number);
    if (isNumber) {
        const numericValue = parseInt(number);
        return Math.abs(numericValue) < 20
            ? numericValue
            : parseInt(number.substring(number.length - 1));
    }
    return null;
}