diff --git a/packages/lexical-html/src/index.ts b/packages/lexical-html/src/index.ts
index 3893b2062bb..8dbf1b416a2 100644
--- a/packages/lexical-html/src/index.ts
+++ b/packages/lexical-html/src/index.ts
@@ -92,7 +92,17 @@ function $appendNodesToHTML(
target = clone;
}
const children = $isElementNode(target) ? target.getChildren() : [];
- const {element, after} = target.exportDOM(editor);
+ const registeredNode = editor._nodes.get(target.getType());
+ let exportOutput;
+
+ // Use HTMLConfig overrides, if available.
+ if (registeredNode && registeredNode.exportDOM !== undefined) {
+ exportOutput = registeredNode.exportDOM(editor, target);
+ } else {
+ exportOutput = target.exportDOM(editor);
+ }
+
+ const {element, after} = exportOutput;
if (!element) {
return false;
diff --git a/packages/lexical-react/flow/LexicalComposer.js.flow b/packages/lexical-react/flow/LexicalComposer.js.flow
index 8c32e5cb133..b4cb35ce9b4 100644
--- a/packages/lexical-react/flow/LexicalComposer.js.flow
+++ b/packages/lexical-react/flow/LexicalComposer.js.flow
@@ -13,6 +13,7 @@ import type {
LexicalNode,
EditorState,
LexicalNodeReplacement,
+ HTMLConfig,
} from 'lexical';
export type InitialEditorStateType =
@@ -29,6 +30,7 @@ export type InitialConfigType = $ReadOnly<{
theme?: EditorThemeClasses,
editorState?: InitialEditorStateType,
onError: (error: Error, editor: LexicalEditor) => void,
+ html?: HTMLConfig,
}>;
type Props = {
diff --git a/packages/lexical-react/src/LexicalComposer.tsx b/packages/lexical-react/src/LexicalComposer.tsx
index be535c00930..8dc7ebc11d5 100644
--- a/packages/lexical-react/src/LexicalComposer.tsx
+++ b/packages/lexical-react/src/LexicalComposer.tsx
@@ -19,6 +19,7 @@ import {
createEditor,
EditorState,
EditorThemeClasses,
+ HTMLConfig,
Klass,
LexicalEditor,
LexicalNode,
@@ -45,6 +46,7 @@ export type InitialConfigType = Readonly<{
editable?: boolean;
theme?: EditorThemeClasses;
editorState?: InitialEditorStateType;
+ html?: HTMLConfig;
}>;
type Props = {
@@ -62,6 +64,7 @@ export function LexicalComposer({initialConfig, children}: Props): JSX.Element {
nodes,
onError,
editorState: initialEditorState,
+ html,
} = initialConfig;
const context: LexicalComposerContextType = createLexicalComposerContext(
@@ -74,6 +77,7 @@ export function LexicalComposer({initialConfig, children}: Props): JSX.Element {
if (editor === null) {
const newEditor = createEditor({
editable: initialConfig.editable,
+ html,
namespace,
nodes,
onError: (error) => onError(error, newEditor),
diff --git a/packages/lexical/flow/Lexical.js.flow b/packages/lexical/flow/Lexical.js.flow
index cb7760fe571..231114d00cc 100644
--- a/packages/lexical/flow/Lexical.js.flow
+++ b/packages/lexical/flow/Lexical.js.flow
@@ -278,6 +278,14 @@ export type LexicalNodeReplacement = {
withKlass?: Class,
};
+export type HTMLConfig = {
+ export?: Map<
+ Class,
+ (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput,
+ >,
+ import?: DOMConversionMap,
+};
+
declare export function createEditor(editorConfig?: {
editorState?: EditorState,
namespace: string,
@@ -287,6 +295,7 @@ declare export function createEditor(editorConfig?: {
onError: (error: Error) => void,
disableEvents?: boolean,
editable?: boolean,
+ html?: HTMLConfig,
}): LexicalEditor;
/**
diff --git a/packages/lexical/src/LexicalEditor.ts b/packages/lexical/src/LexicalEditor.ts
index b2b5c1e1d01..df8600e2693 100644
--- a/packages/lexical/src/LexicalEditor.ts
+++ b/packages/lexical/src/LexicalEditor.ts
@@ -7,7 +7,12 @@
*/
import type {EditorState, SerializedEditorState} from './LexicalEditorState';
-import type {DOMConversion, NodeKey} from './LexicalNode';
+import type {
+ DOMConversion,
+ DOMConversionMap,
+ DOMExportOutput,
+ NodeKey,
+} from './LexicalNode';
import invariant from 'shared/invariant';
@@ -151,6 +156,14 @@ export type LexicalNodeReplacement = {
withKlass?: Klass;
};
+export type HTMLConfig = {
+ export?: Map<
+ Klass,
+ (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput
+ >;
+ import?: DOMConversionMap;
+};
+
export type CreateEditorArgs = {
disableEvents?: boolean;
editorState?: EditorState;
@@ -160,6 +173,7 @@ export type CreateEditorArgs = {
parentEditor?: LexicalEditor;
editable?: boolean;
theme?: EditorThemeClasses;
+ html?: HTMLConfig;
};
export type RegisteredNodes = Map;
@@ -169,6 +183,10 @@ export type RegisteredNode = {
transforms: Set>;
replace: null | ((node: LexicalNode) => LexicalNode);
replaceWithKlass: null | Klass;
+ exportDOM?: (
+ editor: LexicalEditor,
+ targetNode: LexicalNode,
+ ) => DOMExportOutput;
};
export type Transform = (node: T) => void;
@@ -330,9 +348,24 @@ export function resetEditor(
}
}
-function initializeConversionCache(nodes: RegisteredNodes): DOMConversionCache {
+function initializeConversionCache(
+ nodes: RegisteredNodes,
+ additionalConversions?: DOMConversionMap,
+): DOMConversionCache {
const conversionCache = new Map();
const handledConversions = new Set();
+ const addConversionsToCache = (map: DOMConversionMap) => {
+ Object.keys(map).forEach((key) => {
+ let currentCache = conversionCache.get(key);
+
+ if (currentCache === undefined) {
+ currentCache = [];
+ conversionCache.set(key, currentCache);
+ }
+
+ currentCache.push(map[key]);
+ });
+ };
nodes.forEach((node) => {
const importDOM =
node.klass.importDOM != null
@@ -347,18 +380,12 @@ function initializeConversionCache(nodes: RegisteredNodes): DOMConversionCache {
const map = importDOM();
if (map !== null) {
- Object.keys(map).forEach((key) => {
- let currentCache = conversionCache.get(key);
-
- if (currentCache === undefined) {
- currentCache = [];
- conversionCache.set(key, currentCache);
- }
-
- currentCache.push(map[key]);
- });
+ addConversionsToCache(map);
}
});
+ if (additionalConversions) {
+ addConversionsToCache(additionalConversions);
+ }
return conversionCache;
}
@@ -389,9 +416,9 @@ export function createEditor(editorConfig?: CreateEditorArgs): LexicalEditor {
ParagraphNode,
...(config.nodes || []),
];
- const onError = config.onError;
+ const {onError, html} = config;
const isEditable = config.editable !== undefined ? config.editable : true;
- let registeredNodes;
+ let registeredNodes: Map;
if (editorConfig === undefined && activeEditor !== null) {
registeredNodes = activeEditor._nodes;
@@ -457,11 +484,12 @@ export function createEditor(editorConfig?: CreateEditorArgs): LexicalEditor {
}
const type = klass.getType();
const transform = klass.transform();
- const transforms = new Set();
+ const transforms = new Set>();
if (transform !== null) {
transforms.add(transform);
}
registeredNodes.set(type, {
+ exportDOM: html && html.export ? html.export.get(klass) : undefined,
klass,
replace,
replaceWithKlass,
@@ -469,7 +497,6 @@ export function createEditor(editorConfig?: CreateEditorArgs): LexicalEditor {
});
}
}
-
const editor = new LexicalEditor(
editorState,
parentEditor,
@@ -480,7 +507,7 @@ export function createEditor(editorConfig?: CreateEditorArgs): LexicalEditor {
theme,
},
onError ? onError : console.error,
- initializeConversionCache(registeredNodes),
+ initializeConversionCache(registeredNodes, html ? html.import : undefined),
isEditable,
);
diff --git a/packages/lexical/src/index.ts b/packages/lexical/src/index.ts
index a90cd519a8d..f7c61796515 100644
--- a/packages/lexical/src/index.ts
+++ b/packages/lexical/src/index.ts
@@ -16,6 +16,7 @@ export type {
EditableListener,
EditorConfig,
EditorThemeClasses,
+ HTMLConfig,
Klass,
LexicalCommand,
LexicalEditor,