All files / roosterjs-content-model-markdown/lib/modelToMarkdown/creators createMarkdownParagraph.ts

97.78% Statements 44/45
88.57% Branches 31/35
100% Functions 2/2
97.22% Lines 35/36

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 801x                         1x       89x 89x 124x 124x   115x 115x   7x 7x   2x 1x   2x           89x 9x 9x 9x 8x       89x       115x 115x   115x 102x           13x 13x   13x 1x     12x 12x 5x   12x 2x   12x 6x     12x    
import { MarkdownHeadings } from '../../constants/headings';
import type { ContentModelParagraph, ContentModelText } from 'roosterjs-content-model-types';
 
/**
 * @internal
 */
export interface ParagraphContext {
    ignoreLineBreaks: boolean;
}
 
/**
 * @internal
 */
export function createMarkdownParagraph(
    paragraph: ContentModelParagraph,
    context?: ParagraphContext
): string {
    const { segments } = paragraph;
    let markdownString = '';
    for (const segment of segments) {
        switch (segment.segmentType) {
            case 'Text':
                markdownString += textProcessor(segment);
                break;
            case 'Image':
                markdownString += `![${segment.alt || 'image'}](${segment.src})`;
                break;
            case 'Br':
                if (!context?.ignoreLineBreaks) {
                    markdownString += '\n';
                }
                break;
            default:
                break;
        }
    }
 
    if (paragraph.decorator) {
        const { tagName } = paragraph.decorator;
        const prefix = MarkdownHeadings[tagName];
        if (prefix) {
            markdownString = `${prefix}${markdownString}`;
        }
    }
 
    return markdownString;
}
 
function textProcessor(text: ContentModelText): string {
    const { fontWeight, italic, strikethrough } = text.format;
    const hasInlineFormat = fontWeight == 'bold' || italic || strikethrough;
 
    if (!hasInlineFormat) {
        return text.link ? `[${text.text}](${text.link.format.href})` : text.text;
    }
 
    // Move leading/trailing whitespace outside the markers so the emitted
    // markdown is valid (CommonMark requires emphasis markers to hug
    // non-whitespace), e.g. "world " with <b> => " " + "**world**".
    const match = /^(\s*)([\s\S]*?)(\s*)$/.exec(text.text);
    const [, leading, core, trailing] = match ? match : ['', '', text.text, ''];
 
    if (!core) {
        return text.text;
    }
 
    let inner = text.link ? `[${core}](${text.link.format.href})` : core;
    if (fontWeight == 'bold') {
        inner = `**${inner}**`;
    }
    if (strikethrough) {
        inner = `~~${inner}~~`;
    }
    if (italic) {
        inner = `*${inner}*`;
    }
 
    return `${leading}${inner}${trailing}`;
}