UNPKG

phx-react

Version:

PHX REACT

94 lines 3.49 kB
import { $insertGeneratedNodes } from '@lexical/clipboard'; import { $generateNodesFromDOM } from '@lexical/html'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { $createParagraphNode, $createTextNode, $getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, PASTE_COMMAND, } from 'lexical'; import { useEffect } from 'react'; const LEXICAL_MIME_TYPE = 'application/x-lexical-editor'; const PRESENTATIONAL_ATTRIBUTES = ['class', 'id', 'style']; const LEGACY_PRESENTATIONAL_ATTRIBUTES = ['align', 'bgcolor', 'color', 'face', 'size']; const REMOVED_TAGS = ['link', 'meta', 'script', 'style']; /** * Removes unsafe and presentational markup from pasted HTML. * @param doc Parsed clipboard HTML document. */ function sanitizePastedHTMLDocument(doc) { REMOVED_TAGS.forEach((tagName) => { doc.querySelectorAll(tagName).forEach((node) => node.remove()); }); doc.body.querySelectorAll('*').forEach((element) => { PRESENTATIONAL_ATTRIBUTES.forEach((attribute) => { element.removeAttribute(attribute); }); if (element.tagName === 'FONT') { LEGACY_PRESENTATIONAL_ATTRIBUTES.forEach((attribute) => { element.removeAttribute(attribute); }); } }); } /** * Inserts pasted plain text as separate paragraphs when it contains line breaks. * @param text Plain text clipboard content. * @returns True when text was handled as multiple paragraphs. */ function insertMultilinePlainText(text) { if (!/\r|\n/.test(text)) { return false; } const selection = $getSelection(); if (!$isRangeSelection(selection)) { return false; } const paragraphs = text .replace(/\r\n?/g, '\n') .split('\n') .map((line) => { const paragraph = $createParagraphNode(); if (line.length > 0) { paragraph.append($createTextNode(line)); } return paragraph; }); selection.insertNodes(paragraphs); return true; } /** * Registers paste sanitization for the rich text editor. * @returns Null because the plugin only registers editor commands. */ export default function SanitizePastePlugin() { const [editor] = useLexicalComposerContext(); useEffect(() => editor.registerCommand(PASTE_COMMAND, (event) => { if (!('clipboardData' in event) || event.clipboardData == null) { return false; } const { clipboardData } = event; if (clipboardData.types.includes(LEXICAL_MIME_TYPE)) { return false; } const text = clipboardData.getData('text/plain'); if (/\r|\n/.test(text)) { event.preventDefault(); return insertMultilinePlainText(text); } const html = clipboardData.getData('text/html'); if (!html) { return false; } event.preventDefault(); editor.update(() => { const selection = $getSelection(); if (selection == null) { return; } const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); sanitizePastedHTMLDocument(doc); const nodes = $generateNodesFromDOM(editor, doc); $insertGeneratedNodes(editor, nodes, selection); }); return true; }, COMMAND_PRIORITY_HIGH), [editor]); return null; } //# sourceMappingURL=index.js.map