Skip to content

Commit

Permalink
Allow overriding HTML serialization behavior from the editor config. (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
acywatson authored Sep 15, 2023
1 parent 64f9d86 commit 02f01f1
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 18 deletions.
12 changes: 11 additions & 1 deletion packages/lexical-html/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions packages/lexical-react/flow/LexicalComposer.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
LexicalNode,
EditorState,
LexicalNodeReplacement,
HTMLConfig,
} from 'lexical';

export type InitialEditorStateType =
Expand All @@ -29,6 +30,7 @@ export type InitialConfigType = $ReadOnly<{
theme?: EditorThemeClasses,
editorState?: InitialEditorStateType,
onError: (error: Error, editor: LexicalEditor) => void,
html?: HTMLConfig,
}>;

type Props = {
Expand Down
4 changes: 4 additions & 0 deletions packages/lexical-react/src/LexicalComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
createEditor,
EditorState,
EditorThemeClasses,
HTMLConfig,
Klass,
LexicalEditor,
LexicalNode,
Expand All @@ -45,6 +46,7 @@ export type InitialConfigType = Readonly<{
editable?: boolean;
theme?: EditorThemeClasses;
editorState?: InitialEditorStateType;
html?: HTMLConfig;
}>;

type Props = {
Expand All @@ -62,6 +64,7 @@ export function LexicalComposer({initialConfig, children}: Props): JSX.Element {
nodes,
onError,
editorState: initialEditorState,
html,
} = initialConfig;

const context: LexicalComposerContextType = createLexicalComposerContext(
Expand All @@ -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),
Expand Down
9 changes: 9 additions & 0 deletions packages/lexical/flow/Lexical.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,14 @@ export type LexicalNodeReplacement = {
withKlass?: Class<LexicalNode>,
};

export type HTMLConfig = {
export?: Map<
Class<LexicalNode>,
(editor: LexicalEditor, target: LexicalNode) => DOMExportOutput,
>,
import?: DOMConversionMap,
};

declare export function createEditor(editorConfig?: {
editorState?: EditorState,
namespace: string,
Expand All @@ -287,6 +295,7 @@ declare export function createEditor(editorConfig?: {
onError: (error: Error) => void,
disableEvents?: boolean,
editable?: boolean,
html?: HTMLConfig,
}): LexicalEditor;

/**
Expand Down
61 changes: 44 additions & 17 deletions packages/lexical/src/LexicalEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -151,6 +156,14 @@ export type LexicalNodeReplacement = {
withKlass?: Klass<LexicalNode>;
};

export type HTMLConfig = {
export?: Map<
Klass<LexicalNode>,
(editor: LexicalEditor, target: LexicalNode) => DOMExportOutput
>;
import?: DOMConversionMap;
};

export type CreateEditorArgs = {
disableEvents?: boolean;
editorState?: EditorState;
Expand All @@ -160,6 +173,7 @@ export type CreateEditorArgs = {
parentEditor?: LexicalEditor;
editable?: boolean;
theme?: EditorThemeClasses;
html?: HTMLConfig;
};

export type RegisteredNodes = Map<string, RegisteredNode>;
Expand All @@ -169,6 +183,10 @@ export type RegisteredNode = {
transforms: Set<Transform<LexicalNode>>;
replace: null | ((node: LexicalNode) => LexicalNode);
replaceWithKlass: null | Klass<LexicalNode>;
exportDOM?: (
editor: LexicalEditor,
targetNode: LexicalNode,
) => DOMExportOutput;
};

export type Transform<T extends LexicalNode> = (node: T) => void;
Expand Down Expand Up @@ -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
Expand All @@ -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;
}

Expand Down Expand Up @@ -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<string, RegisteredNode>;

if (editorConfig === undefined && activeEditor !== null) {
registeredNodes = activeEditor._nodes;
Expand Down Expand Up @@ -457,19 +484,19 @@ export function createEditor(editorConfig?: CreateEditorArgs): LexicalEditor {
}
const type = klass.getType();
const transform = klass.transform();
const transforms = new Set();
const transforms = new Set<Transform<LexicalNode>>();
if (transform !== null) {
transforms.add(transform);
}
registeredNodes.set(type, {
exportDOM: html && html.export ? html.export.get(klass) : undefined,
klass,
replace,
replaceWithKlass,
transforms,
});
}
}

const editor = new LexicalEditor(
editorState,
parentEditor,
Expand All @@ -480,7 +507,7 @@ export function createEditor(editorConfig?: CreateEditorArgs): LexicalEditor {
theme,
},
onError ? onError : console.error,
initializeConversionCache(registeredNodes),
initializeConversionCache(registeredNodes, html ? html.import : undefined),
isEditable,
);

Expand Down
1 change: 1 addition & 0 deletions packages/lexical/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type {
EditableListener,
EditorConfig,
EditorThemeClasses,
HTMLConfig,
Klass,
LexicalCommand,
LexicalEditor,
Expand Down

2 comments on commit 02f01f1

@vercel
Copy link

@vercel vercel bot commented on 02f01f1 Sep 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

lexical – ./packages/lexical-website

lexical-fbopensource.vercel.app
lexicaljs.com
lexical.dev
lexicaljs.org
lexical-git-main-fbopensource.vercel.app
www.lexical.dev

@vercel
Copy link

@vercel vercel bot commented on 02f01f1 Sep 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

lexical-playground – ./packages/lexical-playground

lexical-playground-git-main-fbopensource.vercel.app
lexical-playground.vercel.app
lexical-playground-fbopensource.vercel.app
playground.lexical.dev

Please sign in to comment.