All files / roosterjs-content-model-markdown/lib/publicApi isPastedContentMarkdown.ts

100% Statements 35/35
95.45% Branches 21/22
100% Functions 4/4
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 801x         1x   1x               1x 54x   27x 3x     24x 22x 4x     18x 18x 18x   18x   2x       18x   18x 24x   24x 4x     20x 3x   3x 1x         13x       26x               18x 18x 18x   18x 17x 17x       18x    
import { isContentMarkdown } from './isContentMarkdown';
import type { ClipboardData, DOMCreator, IEditor } from 'roosterjs-content-model-types';
 
// Tags that are considered "thin wrappers", which only add structure (such as line breaks)
// around the plain text without applying any real formatting to its content.
const ThinWrapperTags = new Set<string>(['DIV', 'P', 'BR', 'SPAN']);
 
const AllowedAttributes = new Set<string>(['class', 'style']);
 
/**
 * Detect whether the given clipboard content can be interpreted as markdown.
 * @param editor The editor instance.
 * @param clipboardData The clipboard data to check.
 * @returns True if the content can be interpreted as markdown, false otherwise.
 */
export function isPastedContentMarkdown(editor: IEditor, clipboardData: ClipboardData): boolean {
    const { text, rawHtml } = clipboardData;
 
    if (!text || !text.trim()) {
        return false;
    }
 
    if (isContentMarkdown(text)) {
        if (!rawHtml) {
            return true;
        }
 
        const doc = editor.getDocument();
        const trustedHTMLHandler = editor.getDOMCreator();
        const fragment = parseHtmlToFragment(rawHtml, doc, trustedHTMLHandler);
 
        return isThinWrapperOfPlainText(fragment, text);
    }
    return false;
}
 
function isThinWrapperOfPlainText(fragment: DocumentFragment, text: string): boolean {
    const elements = fragment.querySelectorAll('*');
 
    for (let i = 0; i < elements.length; i++) {
        const element = elements[i];
 
        if (!ThinWrapperTags.has(element.tagName)) {
            return false;
        }
 
        for (let j = 0; j < element.attributes.length; j++) {
            const attr = element.attributes[j];
 
            if (!AllowedAttributes.has(attr.name) && !attr.name.startsWith('data-')) {
                return false;
            }
        }
    }
 
    return removeWhitespace(fragment.textContent || '') === removeWhitespace(text);
}
 
function removeWhitespace(text: string): string {
    return text.replace(/\s/g, '');
}
 
function parseHtmlToFragment(
    html: string,
    doc: Document,
    trustedHTMLHandler: DOMCreator
): DocumentFragment {
    const parsedDoc = trustedHTMLHandler.htmlToDOM(html);
    const fragment = doc.createDocumentFragment();
    const body = parsedDoc?.body;
 
    if (body) {
        while (body.firstChild) {
            fragment.appendChild(body.firstChild);
        }
    }
 
    return fragment;
}