diff --git a/packages/lexical/src/LexicalEditor.ts b/packages/lexical/src/LexicalEditor.ts index 44ae24f4b6b..e86e7b215e4 100644 --- a/packages/lexical/src/LexicalEditor.ts +++ b/packages/lexical/src/LexicalEditor.ts @@ -11,6 +11,7 @@ import type { DOMConversion, DOMConversionMap, DOMExportOutput, + DOMExportOutputMap, NodeKey, } from './LexicalNode'; @@ -170,10 +171,7 @@ export type LexicalNodeReplacement = { }; export type HTMLConfig = { - export?: Map< - Klass, - (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput - >; + export?: DOMExportOutputMap; import?: DOMConversionMap; }; diff --git a/packages/lexical/src/LexicalNode.ts b/packages/lexical/src/LexicalNode.ts index 9d24f72867b..5f70d3e36df 100644 --- a/packages/lexical/src/LexicalNode.ts +++ b/packages/lexical/src/LexicalNode.ts @@ -148,6 +148,11 @@ export type DOMConversionOutput = { node: null | LexicalNode | Array; }; +export type DOMExportOutputMap = Map< + Klass, + (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput +>; + export type DOMExportOutput = { after?: ( generatedElement: HTMLElement | Text | null | undefined, diff --git a/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx b/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx index cc81f334678..b7714f03532 100644 --- a/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx +++ b/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx @@ -6,6 +6,7 @@ * */ +import {$generateHtmlFromNodes, $generateNodesFromDOM} from '@lexical/html'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {ContentEditable} from '@lexical/react/LexicalContentEditable'; import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary'; @@ -57,6 +58,7 @@ import { } from 'react'; import {createPortal} from 'react-dom'; import {createRoot, Root} from 'react-dom/client'; +import invariant from 'shared/invariant'; import * as ReactTestUtils from 'shared/react-test-utils'; import { @@ -2777,4 +2779,94 @@ describe('LexicalEditor tests', () => { newEditor1.setRootElement(null); newEditor2.setRootElement(null); }); + + describe('html config', () => { + it('should override export output function', async () => { + const onError = jest.fn(); + + const newEditor = createTestEditor({ + html: { + export: new Map([ + [ + TextNode, + (_, target) => { + invariant($isTextNode(target)); + + return { + element: target.hasFormat('bold') + ? document.createElement('bor') + : document.createElement('foo'), + }; + }, + ], + ]), + }, + onError: onError, + }); + + newEditor.setRootElement(container); + + newEditor.update(() => { + const root = $getRoot(); + const paragraph = $createParagraphNode(); + const text = $createTextNode(); + root.append(paragraph); + paragraph.append(text); + + const selection = $createNodeSelection(); + selection.add(text.getKey()); + + const htmlFoo = $generateHtmlFromNodes(newEditor, selection); + expect(htmlFoo).toBe(''); + + text.toggleFormat('bold'); + + const htmlBold = $generateHtmlFromNodes(newEditor, selection); + expect(htmlBold).toBe(''); + }); + + expect(onError).not.toHaveBeenCalled(); + }); + + it('should override import conversion function', async () => { + const onError = jest.fn(); + + const newEditor = createTestEditor({ + html: { + import: { + figure: () => ({ + conversion: () => ({node: $createTextNode('yolo')}), + priority: 4, + }), + }, + }, + onError: onError, + }); + + newEditor.setRootElement(container); + + newEditor.update(() => { + const html = '
'; + + const parser = new DOMParser(); + const dom = parser.parseFromString(html, 'text/html'); + const node = $generateNodesFromDOM(newEditor, dom)[0]; + + expect(node).toEqual({ + __detail: 0, + __format: 0, + __key: node.getKey(), + __mode: 0, + __next: null, + __parent: null, + __prev: null, + __style: '', + __text: 'yolo', + __type: 'text', + }); + }); + + expect(onError).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/lexical/src/__tests__/utils/index.tsx b/packages/lexical/src/__tests__/utils/index.tsx index fac3879975b..5292fdd5a5f 100644 --- a/packages/lexical/src/__tests__/utils/index.tsx +++ b/packages/lexical/src/__tests__/utils/index.tsx @@ -46,7 +46,11 @@ import {createRef} from 'react'; import {createRoot} from 'react-dom/client'; import * as ReactTestUtils from 'shared/react-test-utils'; -import {CreateEditorArgs, LexicalNodeReplacement} from '../../LexicalEditor'; +import { + CreateEditorArgs, + HTMLConfig, + LexicalNodeReplacement, +} from '../../LexicalEditor'; import {resetRandomKey} from '../../LexicalUtils'; const prettierConfig = prettier.resolveConfig.sync( @@ -520,6 +524,7 @@ export function createTestEditor( onError?: (error: Error) => void; disableEvents?: boolean; readOnly?: boolean; + html?: HTMLConfig; } = {}, ): LexicalEditor { const customNodes = config.nodes || [];