From 82f9fa4bb87752ee3fbfe07e2041c08c2c26808f Mon Sep 17 00:00:00 2001 From: Acy Watson Date: Sun, 2 Apr 2023 18:30:24 -0600 Subject: [PATCH 1/6] add serialization APIs to create editor --- packages/lexical/src/LexicalEditor.ts | 46 +++++++++++++++++++-------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/lexical/src/LexicalEditor.ts b/packages/lexical/src/LexicalEditor.ts index b2b5c1e1d01..77bcd1dbcd9 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'; @@ -160,6 +165,10 @@ export type CreateEditorArgs = { parentEditor?: LexicalEditor; editable?: boolean; theme?: EditorThemeClasses; + html?: { + export?: Map, DOMExportOutput>; + import?: DOMConversionMap; + }; }; export type RegisteredNodes = Map; @@ -330,9 +339,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 +371,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; } @@ -469,7 +487,7 @@ export function createEditor(editorConfig?: CreateEditorArgs): LexicalEditor { }); } } - + const {html} = config; const editor = new LexicalEditor( editorState, parentEditor, @@ -480,7 +498,7 @@ export function createEditor(editorConfig?: CreateEditorArgs): LexicalEditor { theme, }, onError ? onError : console.error, - initializeConversionCache(registeredNodes), + initializeConversionCache(registeredNodes, html ? html.import : undefined), isEditable, ); From 8a295c4d823ca61950d5fbf2e3eeff9be0369b6b Mon Sep 17 00:00:00 2001 From: Acy Watson Date: Sun, 2 Apr 2023 22:30:27 -0600 Subject: [PATCH 2/6] allow overriding html serialization via editor config --- packages/lexical-html/src/index.ts | 10 +++++++++- packages/lexical/src/LexicalEditor.ts | 6 +++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/lexical-html/src/index.ts b/packages/lexical-html/src/index.ts index 3893b2062bb..39522ac3e2d 100644 --- a/packages/lexical-html/src/index.ts +++ b/packages/lexical-html/src/index.ts @@ -92,7 +92,15 @@ 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; + 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/src/LexicalEditor.ts b/packages/lexical/src/LexicalEditor.ts index 77bcd1dbcd9..8f042ff52e0 100644 --- a/packages/lexical/src/LexicalEditor.ts +++ b/packages/lexical/src/LexicalEditor.ts @@ -166,7 +166,7 @@ export type CreateEditorArgs = { editable?: boolean; theme?: EditorThemeClasses; html?: { - export?: Map, DOMExportOutput>; + export?: Map, () => DOMExportOutput>; import?: DOMConversionMap; }; }; @@ -178,6 +178,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; From 8545261357e83ddf99488bca5ad57036615dc3bc Mon Sep 17 00:00:00 2001 From: Acy Watson Date: Thu, 7 Sep 2023 08:41:09 -0600 Subject: [PATCH 3/6] fix exports --- packages/lexical-html/src/index.ts | 2 ++ .../lexical-react/src/LexicalComposer.tsx | 4 ++++ packages/lexical/src/LexicalEditor.ts | 21 ++++++++++++------- packages/lexical/src/index.ts | 1 + 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/lexical-html/src/index.ts b/packages/lexical-html/src/index.ts index 39522ac3e2d..8dbf1b416a2 100644 --- a/packages/lexical-html/src/index.ts +++ b/packages/lexical-html/src/index.ts @@ -94,6 +94,8 @@ function $appendNodesToHTML( const children = $isElementNode(target) ? target.getChildren() : []; 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 { 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/src/LexicalEditor.ts b/packages/lexical/src/LexicalEditor.ts index 8f042ff52e0..0d6581a3e5b 100644 --- a/packages/lexical/src/LexicalEditor.ts +++ b/packages/lexical/src/LexicalEditor.ts @@ -154,6 +154,14 @@ export type LexicalNodeReplacement = { node: InstanceType, ) => LexicalNode; withKlass?: Klass; +} + +export type HTMLConfig = { + export?: Map< + Klass, + (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput + >; + import?: DOMConversionMap; }; export type CreateEditorArgs = { @@ -165,10 +173,7 @@ export type CreateEditorArgs = { parentEditor?: LexicalEditor; editable?: boolean; theme?: EditorThemeClasses; - html?: { - export?: Map, () => DOMExportOutput>; - import?: DOMConversionMap; - }; + html?: HTMLConfig; }; export type RegisteredNodes = Map; @@ -411,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; @@ -479,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, @@ -491,7 +497,6 @@ export function createEditor(editorConfig?: CreateEditorArgs): LexicalEditor { }); } } - const {html} = config; const editor = new LexicalEditor( editorState, parentEditor, 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, From 8a874b00cae019b0fc7ae5b5387e28aced7d6ca6 Mon Sep 17 00:00:00 2001 From: Acy Watson Date: Thu, 7 Sep 2023 16:38:18 -0600 Subject: [PATCH 4/6] flow types --- packages/lexical/flow/Lexical.js.flow | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/lexical/flow/Lexical.js.flow b/packages/lexical/flow/Lexical.js.flow index cb7760fe571..9a376131fb2 100644 --- a/packages/lexical/flow/Lexical.js.flow +++ b/packages/lexical/flow/Lexical.js.flow @@ -276,6 +276,14 @@ export type LexicalNodeReplacement = { replace: Class, with: (node: LexicalNode) => LexicalNode, withKlass?: Class, +} + +export type HTMLConfig = { + export?: Map< + Class, + (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput, + >, + import?: DOMConversionMap, }; declare export function createEditor(editorConfig?: { @@ -287,6 +295,7 @@ declare export function createEditor(editorConfig?: { onError: (error: Error) => void, disableEvents?: boolean, editable?: boolean, + html?: HTMLConfig, }): LexicalEditor; /** From a1e130fd5b3bb8646e213614ccfc00169cb64c6f Mon Sep 17 00:00:00 2001 From: Acy Watson Date: Thu, 7 Sep 2023 16:39:42 -0600 Subject: [PATCH 5/6] flow types --- packages/lexical-react/flow/LexicalComposer.js.flow | 2 ++ 1 file changed, 2 insertions(+) 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 = { From b9c646eced82016accbb04473dad2a98bba72c3a Mon Sep 17 00:00:00 2001 From: Acy Watson Date: Thu, 14 Sep 2023 10:01:53 -0600 Subject: [PATCH 6/6] prettier --- packages/lexical/flow/Lexical.js.flow | 2 +- packages/lexical/src/LexicalEditor.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lexical/flow/Lexical.js.flow b/packages/lexical/flow/Lexical.js.flow index 9a376131fb2..231114d00cc 100644 --- a/packages/lexical/flow/Lexical.js.flow +++ b/packages/lexical/flow/Lexical.js.flow @@ -276,7 +276,7 @@ export type LexicalNodeReplacement = { replace: Class, with: (node: LexicalNode) => LexicalNode, withKlass?: Class, -} +}; export type HTMLConfig = { export?: Map< diff --git a/packages/lexical/src/LexicalEditor.ts b/packages/lexical/src/LexicalEditor.ts index 0d6581a3e5b..df8600e2693 100644 --- a/packages/lexical/src/LexicalEditor.ts +++ b/packages/lexical/src/LexicalEditor.ts @@ -154,7 +154,7 @@ export type LexicalNodeReplacement = { node: InstanceType, ) => LexicalNode; withKlass?: Klass; -} +}; export type HTMLConfig = { export?: Map<