HTML to Lexical Content <img> tag conversion issue
Link to reproduction
Payload Version
3.0.0-beta.90
Node Version
v20
Next.js Version
15.0.0-canary.121
Describe the Bug
When I convert HTML to Lexical the images are not converted. There is a way to handle it?
Reproduction Steps
Code I'm using to convert HTML -> Lexical
import { createHeadlessEditor } from '@lexical/headless';
import { $generateNodesFromDOM } from '@lexical/html';
import { getEnabledNodes, sanitizeServerEditorConfig } from '@payloadcms/richtext-lexical';
import { JSDOM } from 'jsdom';
import { $getRoot, $getSelection } from 'lexical';
import { defaultEditorConfig } from '@payloadcms/richtext-lexical'
import configPromise from '@payload-config'
const convertHTMLToLexicalNodes = async (htmlString: string) => {
const yourEditorConfig = defaultEditorConfig
const headlessEditor = createHeadlessEditor({
nodes: getEnabledNodes({
editorConfig: await sanitizeServerEditorConfig(yourEditorConfig, await configPromise),
}),
})
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()
// Clear Editor state
headlessEditor.update(() => {
const root = $getRoot();
root.clear();
}, { discrete: true });
return editorJSON;
};
Original HTML
<p>Lorem Ipsum</p><img src="https://upload.wikimedia.org/wikipedia/commons/7/76/RMS_Republic.jpg">
Converted Lexical (JSON) without image
{
"root":{
"children":[
{
"children":[
{
"detail":0,
"format":0,
"mode":"normal",
"style":"",
"text":"Lorem Ipsum",
"type":"text",
"version":1
}
],
"direction":null,
"format":"",
"indent":0,
"type":"paragraph",
"version":1,
"textFormat":0,
"textStyle":""
}
],
"direction":null,
"format":"",
"indent":0,
"type":"root",
"version":1
}
}
Adapters and Plugins
"@payloadcms/db-mongodb": "beta", "@payloadcms/email-nodemailer": "beta", "@payloadcms/next": "beta", "@payloadcms/plugin-cloud-storage": "beta", "@payloadcms/plugin-form-builder": "beta", "@payloadcms/plugin-nested-docs": "beta", "@payloadcms/plugin-seo": "beta", "@payloadcms/richtext-lexical": "beta", "@payloadcms/storage-s3": "beta", "@payloadcms/ui": "beta",
This is currently expected, as the converter cannot auto-upload those images for you. Probably won't add this functionality - don't want this function to perform any modifications to your payload db.
Though there are other strategies to convert html images => lexical (work with JSON, or deploy your own script first that performs those auto-uploads). I've marked this issue as documentation and will add something to our docs
This is currently expected, as the converter cannot auto-upload those images for you. Probably won't add this functionality - don't want this function to perform any modifications to your payload db.
Though there are other strategies to convert html images => lexical (work with JSON, or deploy your own script first that performs those auto-uploads). I've marked this issue as
documentationand will add something to our docs
Thanks Alessio for the reply. I can intercept the url of the images and load them with a script in parallel but I don't understand how to insert the image node with the correct id in the correct location of the Lexical JSON. Is there a way to sneak into the conversion script?
Hi @matteo-naif, I'm having trouble getting the headless editor to parse A-nodes. On line 7 you import from @paylod-config. Could you share that file with me? 🙏
Hi @madsbertelsen , the import is an alias that refers to my payload.config.ts
import { layoutBlocks } from '@/app/(payload)/_config/fields/layout/layoutBlocks'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { BlocksFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
import { locales } from 'locale.config'
import path from 'path'
import { buildConfig } from 'payload'
import { en } from 'payload/i18n/en'
import { it } from 'payload/i18n/it'
import sharp from 'sharp'
import { fileURLToPath } from 'url'
export default buildConfig({
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
BlocksFeature({
blocks: layoutBlocks,
}),
]
}),
collections: [
// Collections
],
globals: [
// Globals
],
secret: process.env.PAYLOAD_SECRET || '',
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
db: mongooseAdapter({
url: process.env.MONGODB_URI || '',
}),
i18n: {
supportedLanguages: { it, en },
},
localization: {
locales: locales.map(l => ({ label: l.label, code: l.code })),
defaultLocale: locales[0].code,
fallback: false,
},
debug: process.env.NODE_ENV === 'development',
sharp,
plugins: [
// Plugins
]
})
Thanks @matteo-naif! Passing the payload config as argument made it work for me 🥳
@AlessioGr any updates?
i just had the same issue and i created my own ImageNode and added it to the Headless Editor. Here is how to add custom nodes
const editor = createHeadlessEditor({
nodes: [
...getEnabledNodes({
editorConfig: editorConfig,
}),
ImageNode,
],
})
and a custom node is a class which you can get from the lexical docs
class ImageNode extends DecoratorNode<null> {.....
you can add all your custom logic there and even upload images and return to the exact spot it was parsed from
createHeadlessEditor
Can we get the solution, please? Looks like it's too complicated to make a custom node.