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 27x         27x     1x         1x       1x         33x 33x 33x 33x 33x 7x 7x 7x 7x           5x     26x 26x 26x 15x       33x 20x             20x 20x     33x     59x 33x 33x 33x 27x 27x       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 */)) !== null) &&
                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 !== null && 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;
}