phx-react
Version:
PHX REACT
94 lines • 3.49 kB
JavaScript
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