diff --git a/package-lock.json b/package-lock.json index d01848e0336..d0e83273504 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "@types/prismjs": "^1.26.0", "@types/react": "^18.0.8", "@types/react-dom": "^18.0.3", + "@types/trusted-types": "^2.0.7", "@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/parser": "^7.8.0", "child-process-promise": "^2.2.1", @@ -8357,6 +8358,13 @@ "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", "dev": true }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -42704,6 +42712,12 @@ "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", "dev": true }, + "@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true + }, "@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", diff --git a/package.json b/package.json index e50d1759cb0..e8b913dd2cd 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "@types/prismjs": "^1.26.0", "@types/react": "^18.0.8", "@types/react-dom": "^18.0.3", + "@types/trusted-types": "^2.0.7", "@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/parser": "^7.8.0", "child-process-promise": "^2.2.1", diff --git a/packages/lexical-clipboard/src/clipboard.ts b/packages/lexical-clipboard/src/clipboard.ts index b8511cbd619..3de6860e998 100644 --- a/packages/lexical-clipboard/src/clipboard.ts +++ b/packages/lexical-clipboard/src/clipboard.ts @@ -154,7 +154,10 @@ export function $insertDataTransferForRichText( if (htmlString) { try { const parser = new DOMParser(); - const dom = parser.parseFromString(htmlString, 'text/html'); + const dom = parser.parseFromString( + trustHTML(htmlString) as string, + 'text/html', + ); const nodes = $generateNodesFromDOM(editor, dom); return $insertGeneratedNodes(editor, nodes, selection); } catch { @@ -192,6 +195,16 @@ export function $insertDataTransferForRichText( } } +function trustHTML(html: string): string | TrustedHTML { + if (window.trustedTypes && window.trustedTypes.createPolicy) { + const policy = window.trustedTypes.createPolicy('lexical', { + createHTML: (input) => input, + }); + return policy.createHTML(html); + } + return html; +} + /** * Inserts Lexical nodes into the editor using different strategies depending on * some simple selection-based heuristics. If you're looking for a generic way to diff --git a/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawComponent.tsx b/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawComponent.tsx index 646d0004a38..5a45068469e 100644 --- a/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawComponent.tsx +++ b/packages/lexical-playground/src/nodes/ExcalidrawNode/ExcalidrawComponent.tsx @@ -11,6 +11,7 @@ import type {NodeKey} from 'lexical'; import {AppState, BinaryFiles} from '@excalidraw/excalidraw/types/types'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; +import {useLexicalEditable} from '@lexical/react/useLexicalEditable'; import {useLexicalNodeSelection} from '@lexical/react/useLexicalNodeSelection'; import {mergeRegister} from '@lexical/utils'; import { @@ -40,6 +41,7 @@ export default function ExcalidrawComponent({ height: 'inherit' | number; }): JSX.Element { const [editor] = useLexicalComposerContext(); + const isEditable = useLexicalEditable(); const [isModalOpen, setModalOpen] = useState( data === '[]' && editor.isEditable(), ); @@ -66,16 +68,13 @@ export default function ExcalidrawComponent({ [editor, isSelected, nodeKey], ); - // Set editor to readOnly if Excalidraw is open to prevent unwanted changes useEffect(() => { - if (isModalOpen) { - editor.setEditable(false); - } else { - editor.setEditable(true); + if (!isEditable) { + if (isSelected) { + clearSelection(); + } + return; } - }, [isModalOpen, editor]); - - useEffect(() => { return mergeRegister( editor.registerCommand( CLICK_COMMAND, @@ -113,7 +112,15 @@ export default function ExcalidrawComponent({ COMMAND_PRIORITY_LOW, ), ); - }, [clearSelection, editor, isSelected, isResizing, $onDelete, setSelected]); + }, [ + clearSelection, + editor, + isSelected, + isResizing, + $onDelete, + setSelected, + isEditable, + ]); const deleteNode = useCallback(() => { setModalOpen(false); @@ -130,9 +137,6 @@ export default function ExcalidrawComponent({ aps: Partial, fls: BinaryFiles, ) => { - if (!editor.isEditable()) { - return; - } return editor.update(() => { const node = $getNodeByKey(nodeKey); if ($isExcalidrawNode(node)) { @@ -198,20 +202,21 @@ export default function ExcalidrawComponent({ return ( <> - { - editor.setEditable(true); - setData(els, aps, fls); - setModalOpen(false); - }} - closeOnClickOutside={false} - /> + {isEditable && isModalOpen && ( + { + setData(els, aps, fls); + setModalOpen(false); + }} + closeOnClickOutside={false} + /> + )} {elements.length > 0 && (