diff --git a/docs/rich-text/converters.mdx b/docs/rich-text/converters.mdx
index 3625edc6e..5c49aa715 100644
--- a/docs/rich-text/converters.mdx
+++ b/docs/rich-text/converters.mdx
@@ -338,8 +338,8 @@ export const UploadFeature: FeatureProviderProviderServer<
Lexical provides a seamless way to perform conversions between various other formats:
-- HTML to Lexical (or, importing HTML into the lexical editor)
-- Markdown to Lexical (or, importing Markdown into the lexical editor)
+- HTML to Lexical
+- Markdown to Lexical
- Lexical to Markdown
A headless editor can perform such conversions outside of the main editor instance. Follow this method to initiate a headless editor:
@@ -455,46 +455,22 @@ export const MyCollection: CollectionConfig = {
## HTML => Lexical
-Once you have your headless editor instance, you can use it to convert HTML to Lexical:
+If you have access to the Payload Config and the lexical editor config, you can convert HTML to the lexical editor state with the following:
```ts
-import { $generateNodesFromDOM } from '@payloadcms/richtext-lexical/lexical/html'
-import { $getRoot, $getSelection } from '@payloadcms/richtext-lexical/lexical'
+import { convertHTMLToLexical, editorConfigFactory } from '@payloadcms/richtext-lexical'
+// Make sure you have jsdom and @types/jsdom installed
import { JSDOM } from 'jsdom'
-headlessEditor.update(
- () => {
- // In a headless environment you can use a package such as JSDom to parse the HTML string.
- const dom = new JSDOM(htmlString)
-
- // Once you have the DOM instance it's easy to generate LexicalNodes.
- const nodes = $generateNodesFromDOM(headlessEditor, dom.window.document)
-
- // Select the root
- $getRoot().select()
-
- // Insert them at a selection.
- const selection = $getSelection()
- selection.insertNodes(nodes)
- },
- { discrete: true },
-)
-
-// Do this if you then want to get the editor JSON
-const editorJSON = headlessEditor.getEditorState().toJSON()
+const html = convertHTMLToLexical({
+ editorConfig: await editorConfigFactory.default({
+ config, // <= make sure you have access to your Payload Config
+ }),
+ html: '
text
',
+ JSDOM, // pass the JSDOM import. As it's a relatively large package, richtext-lexical does not include it by default.
+})
```
-Functions prefixed with a `$` can only be run inside an `editor.update()` or `editorState.read()` callback.
-
-This has been taken from the [lexical serialization & deserialization docs](https://lexical.dev/docs/concepts/serialization#html---lexical).
-
-
- **Note:**
-
- Using the `discrete: true` flag ensures instant updates to the editor state. If
- immediate reading of the updated state isn't necessary, you can omit the flag.
-
-
## Markdown => Lexical
Convert markdown content to the Lexical editor format with the following:
@@ -516,6 +492,17 @@ headlessEditor.update(
const editorJSON = headlessEditor.getEditorState().toJSON()
```
+Functions prefixed with a `$` can only be run inside an `editor.update()` or `editorState.read()` callback.
+
+This has been taken from the [lexical serialization & deserialization docs](https://lexical.dev/docs/concepts/serialization#html---lexical).
+
+
+ **Note:**
+
+ Using the `discrete: true` flag ensures instant updates to the editor state. If
+ immediate reading of the updated state isn't necessary, you can omit the flag.
+
+
## Lexical => Markdown
Export content from the Lexical editor into Markdown format using these steps:
@@ -564,7 +551,6 @@ Here's the code for it:
```ts
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
-
import { $getRoot } from '@payloadcms/richtext-lexical/lexical'
const yourEditorState: SerializedEditorState // <= your current editor state here
diff --git a/packages/richtext-lexical/src/features/converters/htmlToLexical/index.ts b/packages/richtext-lexical/src/features/converters/htmlToLexical/index.ts
new file mode 100644
index 000000000..1db2425df
--- /dev/null
+++ b/packages/richtext-lexical/src/features/converters/htmlToLexical/index.ts
@@ -0,0 +1,50 @@
+import { createHeadlessEditor } from '@lexical/headless'
+import { $getRoot, $getSelection, type SerializedLexicalNode } from 'lexical'
+
+import type { SanitizedServerEditorConfig } from '../../../lexical/config/types.js'
+import type { DefaultNodeTypes, TypedEditorState } from '../../../nodeTypes.js'
+
+import {} from '../../../lexical/config/server/sanitize.js'
+import { getEnabledNodes } from '../../../lexical/nodes/index.js'
+import { $generateNodesFromDOM } from '../../../lexical-proxy/@lexical-html.js'
+
+export const convertHTMLToLexical = ({
+ editorConfig,
+ html,
+ JSDOM,
+}: {
+ editorConfig: SanitizedServerEditorConfig
+ html: string
+ JSDOM: new (html: string) => {
+ window: {
+ document: Document
+ }
+ }
+}): TypedEditorState => {
+ const headlessEditor = createHeadlessEditor({
+ nodes: getEnabledNodes({
+ editorConfig,
+ }),
+ })
+
+ headlessEditor.update(
+ () => {
+ const dom = new JSDOM(html)
+
+ const nodes = $generateNodesFromDOM(headlessEditor, dom.window.document)
+
+ $getRoot().select()
+
+ const selection = $getSelection()
+ if (selection === null) {
+ throw new Error('Selection is null')
+ }
+ selection.insertNodes(nodes)
+ },
+ { discrete: true },
+ )
+
+ const editorJSON = headlessEditor.getEditorState().toJSON()
+
+ return editorJSON as TypedEditorState
+}
diff --git a/packages/richtext-lexical/src/index.ts b/packages/richtext-lexical/src/index.ts
index 48630bc21..e3d3acef7 100644
--- a/packages/richtext-lexical/src/index.ts
+++ b/packages/richtext-lexical/src/index.ts
@@ -886,48 +886,49 @@ export {
HTMLConverterFeature,
type HTMLConverterFeatureProps,
} from './features/converters/html/index.js'
+export { convertHTMLToLexical } from './features/converters/htmlToLexical/index.js'
export { TestRecorderFeature } from './features/debug/testRecorder/server/index.js'
export { TreeViewFeature } from './features/debug/treeView/server/index.js'
export { EXPERIMENTAL_TableFeature } from './features/experimental_table/server/index.js'
export { BoldFeature } from './features/format/bold/feature.server.js'
export { InlineCodeFeature } from './features/format/inlineCode/feature.server.js'
-export { ItalicFeature } from './features/format/italic/feature.server.js'
+export { ItalicFeature } from './features/format/italic/feature.server.js'
export { StrikethroughFeature } from './features/format/strikethrough/feature.server.js'
export { SubscriptFeature } from './features/format/subscript/feature.server.js'
export { SuperscriptFeature } from './features/format/superscript/feature.server.js'
export { UnderlineFeature } from './features/format/underline/feature.server.js'
export { HeadingFeature, type HeadingFeatureProps } from './features/heading/server/index.js'
export { HorizontalRuleFeature } from './features/horizontalRule/server/index.js'
+
export { IndentFeature } from './features/indent/server/index.js'
export { AutoLinkNode } from './features/link/nodes/AutoLinkNode.js'
-
export { LinkNode } from './features/link/nodes/LinkNode.js'
export type { LinkFields } from './features/link/nodes/types.js'
export { LinkFeature, type LinkFeatureServerProps } from './features/link/server/index.js'
export { ChecklistFeature } from './features/lists/checklist/server/index.js'
export { OrderedListFeature } from './features/lists/orderedList/server/index.js'
+
export { UnorderedListFeature } from './features/lists/unorderedList/server/index.js'
export type {
SlateNode,
SlateNodeConverter,
} from './features/migrations/slateToLexical/converter/types.js'
-
export { ParagraphFeature } from './features/paragraph/server/index.js'
export {
RelationshipFeature,
type RelationshipFeatureProps,
} from './features/relationship/server/index.js'
+
export {
type RelationshipData,
RelationshipServerNode,
} from './features/relationship/server/nodes/RelationshipNode.js'
-
export { FixedToolbarFeature } from './features/toolbars/fixed/server/index.js'
-export { InlineToolbarFeature } from './features/toolbars/inline/server/index.js'
+export { InlineToolbarFeature } from './features/toolbars/inline/server/index.js'
export type { ToolbarGroup, ToolbarGroupItem } from './features/toolbars/types.js'
export type {
BaseClientFeatureProps,
@@ -942,6 +943,7 @@ export type {
SanitizedClientFeatures,
SanitizedPlugin,
} from './features/typesClient.js'
+
export type {
AfterChangeNodeHook,
AfterChangeNodeHookArgs,
@@ -967,37 +969,36 @@ export type {
export { createNode } from './features/typeUtilities.js' // Only useful in feature.server.ts
export { UploadFeature } from './features/upload/server/feature.server.js'
-
export type { UploadFeatureProps } from './features/upload/server/feature.server.js'
-export { type UploadData, UploadServerNode } from './features/upload/server/nodes/UploadNode.js'
+export { type UploadData, UploadServerNode } from './features/upload/server/nodes/UploadNode.js'
export type { EditorConfigContextType } from './lexical/config/client/EditorConfigProvider.js'
+
export {
defaultEditorConfig,
defaultEditorFeatures,
defaultEditorLexicalConfig,
} from './lexical/config/server/default.js'
-
export { loadFeatures, sortFeaturesForOptimalLoading } from './lexical/config/server/loader.js'
+
export {
sanitizeServerEditorConfig,
sanitizeServerFeatures,
} from './lexical/config/server/sanitize.js'
-
export type {
ClientEditorConfig,
SanitizedClientEditorConfig,
SanitizedServerEditorConfig,
ServerEditorConfig,
} from './lexical/config/types.js'
-export { getEnabledNodes, getEnabledNodesFromServerNodes } from './lexical/nodes/index.js'
export type { AdapterProps }
+export { getEnabledNodes, getEnabledNodesFromServerNodes } from './lexical/nodes/index.js'
+
export type {
SlashMenuGroup,
SlashMenuItem,
} from './lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
-
export {
DETAIL_TYPE_TO_DETAIL,
DOUBLE_LINE_BREAK,
@@ -1012,26 +1013,26 @@ export {
TEXT_TYPE_TO_FORMAT,
TEXT_TYPE_TO_MODE,
} from './lexical/utils/nodeFormat.js'
+
export { sanitizeUrl, validateUrl } from './lexical/utils/url.js'
export type * from './nodeTypes.js'
export { $convertFromMarkdownString } from './packages/@lexical/markdown/index.js'
-
export { defaultRichTextValue } from './populateGraphQL/defaultValue.js'
export { populate } from './populateGraphQL/populate.js'
+
export type { LexicalEditorProps, LexicalFieldAdminProps, LexicalRichTextAdapter } from './types.js'
export { createServerFeature } from './utilities/createServerFeature.js'
-
export { editorConfigFactory } from './utilities/editorConfigFactory.js'
export type { FieldsDrawerProps } from './utilities/fieldsDrawer/Drawer.js'
export { extractPropsFromJSXPropsString } from './utilities/jsx/extractPropsFromJSXPropsString.js'
+
export {
extractFrontmatter,
frontmatterToObject,
objectToFrontmatter,
propsToJSXString,
} from './utilities/jsx/jsx.js'
-
export { upgradeLexicalData } from './utilities/upgradeLexicalData/index.js'