fix(richtext-lexical)!: fix output of internal list HTML converter (#5827)
BREAKING: Changes the classnames of the converted HTML
This commit is contained in:
@@ -235,6 +235,19 @@ This method employs `convertLexicalToHTML` from `@payloadcms/richtext-lexical`,
|
|||||||
|
|
||||||
Because every `Feature` is able to provide html converters, and because the `htmlFeature` can modify those or provide their own, we need to consolidate them with the default html Converters using the `consolidateHTMLConverters` function.
|
Because every `Feature` is able to provide html converters, and because the `htmlFeature` can modify those or provide their own, we need to consolidate them with the default html Converters using the `consolidateHTMLConverters` function.
|
||||||
|
|
||||||
|
#### CSS
|
||||||
|
|
||||||
|
Payload's lexical HTML converter does not generate CSS for you, but it does add classes to the generated HTML. You can use these classes to style the HTML in your frontend.
|
||||||
|
|
||||||
|
Here is some "base" CSS you can use to ensure that nested lists render correctly:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Base CSS for Lexical HTML */
|
||||||
|
.nestedListItem, .list-check {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Creating your own HTML Converter
|
#### Creating your own HTML Converter
|
||||||
|
|
||||||
HTML Converters are typed as `HTMLConverter`, which contains the node type it should handle, and a function that accepts the serialized node from the lexical editor, and outputs the HTML string. Here's the HTML Converter of the Upload node as an example:
|
HTML Converters are typed as `HTMLConverter`, which contains the node type it should handle, and a function that accepts the serialized node from the lexical editor, and outputs the HTML string. Here's the HTML Converter of the Upload node as an example:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { SerializedEditorState } from 'lexical'
|
import type { SerializedEditorState } from 'lexical'
|
||||||
import type { Field, RichTextField, TextField } from 'payload/types'
|
import type { Field, RichTextField } from 'payload/types'
|
||||||
|
|
||||||
import type { AdapterProps, LexicalRichTextAdapter } from '../../../../../types.js'
|
import type { AdapterProps, LexicalRichTextAdapter } from '../../../../../types.js'
|
||||||
import type { SanitizedServerEditorConfig } from '../../../../lexical/config/types.js'
|
import type { SanitizedServerEditorConfig } from '../../../../lexical/config/types.js'
|
||||||
@@ -10,6 +10,12 @@ import { defaultHTMLConverters } from '../converter/defaultConverters.js'
|
|||||||
import { convertLexicalToHTML } from '../converter/index.js'
|
import { convertLexicalToHTML } from '../converter/index.js'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
/**
|
||||||
|
* Whether the lexicalHTML field should be hidden in the admin panel
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
hidden?: boolean
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,13 +59,16 @@ export const lexicalHTML: (
|
|||||||
**/
|
**/
|
||||||
lexicalFieldName: string,
|
lexicalFieldName: string,
|
||||||
props: Props,
|
props: Props,
|
||||||
) => TextField = (lexicalFieldName, props) => {
|
) => Field = (lexicalFieldName, props) => {
|
||||||
const { name = 'lexicalHTML' } = props
|
const { name = 'lexicalHTML', hidden = true } = props
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
type: 'text',
|
type: 'code',
|
||||||
admin: {
|
admin: {
|
||||||
hidden: true,
|
editorOptions: {
|
||||||
|
language: 'html',
|
||||||
|
},
|
||||||
|
hidden,
|
||||||
},
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
afterRead: [
|
afterRead: [
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { SerializedListItemNode, SerializedListNode } from '@lexical/list'
|
import type { SerializedListItemNode, SerializedListNode } from '@lexical/list'
|
||||||
|
|
||||||
import lexicalListImport from '@lexical/list'
|
import lexicalListImport from '@lexical/list'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
const { ListItemNode, ListNode } = lexicalListImport
|
const { ListItemNode, ListNode } = lexicalListImport
|
||||||
|
|
||||||
import type { HTMLConverter } from '../converters/html/converter/types.js'
|
import type { HTMLConverter } from '../converters/html/converter/types.js'
|
||||||
@@ -19,13 +20,15 @@ export const ListHTMLConverter: HTMLConverter<SerializedListNode> = {
|
|||||||
payload,
|
payload,
|
||||||
})
|
})
|
||||||
|
|
||||||
return `<${node?.tag} class="${node?.listType}">${childrenText}</${node?.tag}>`
|
return `<${node?.tag} class="list-${node?.listType}">${childrenText}</${node?.tag}>`
|
||||||
},
|
},
|
||||||
nodeTypes: [ListNode.getType()],
|
nodeTypes: [ListNode.getType()],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ListItemHTMLConverter: HTMLConverter<SerializedListItemNode> = {
|
export const ListItemHTMLConverter: HTMLConverter<SerializedListItemNode> = {
|
||||||
converter: async ({ converters, node, parent, payload }) => {
|
converter: async ({ converters, node, parent, payload }) => {
|
||||||
|
const hasSubLists = node.children.some((child) => child.type === 'list')
|
||||||
|
|
||||||
const childrenText = await convertLexicalNodesToHTML({
|
const childrenText = await convertLexicalNodesToHTML({
|
||||||
converters,
|
converters,
|
||||||
lexicalNodes: node.children,
|
lexicalNodes: node.children,
|
||||||
@@ -37,19 +40,30 @@ export const ListItemHTMLConverter: HTMLConverter<SerializedListItemNode> = {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if ('listType' in parent && parent?.listType === 'check') {
|
if ('listType' in parent && parent?.listType === 'check') {
|
||||||
|
const uuid = uuidv4()
|
||||||
|
|
||||||
return `<li aria-checked=${node.checked ? 'true' : 'false'} class="${
|
return `<li aria-checked=${node.checked ? 'true' : 'false'} class="${
|
||||||
'list-item-checkbox' + node.checked
|
'list-item-checkbox' +
|
||||||
? 'list-item-checkbox-checked'
|
(node.checked ? ' list-item-checkbox-checked' : ' list-item-checkbox-unchecked') +
|
||||||
: 'list-item-checkbox-unchecked'
|
(hasSubLists ? ' nestedListItem' : '')
|
||||||
}"
|
}"
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
tabIndex=${-1}
|
tabIndex=${-1}
|
||||||
value=${node?.value}
|
value=${node?.value}
|
||||||
>
|
>
|
||||||
${childrenText}
|
${
|
||||||
|
hasSubLists
|
||||||
|
? childrenText
|
||||||
|
: `
|
||||||
|
<input type="checkbox" id="${uuid}"${node.checked ? ' checked' : ''}>
|
||||||
|
<label for="${uuid}">${childrenText}</label><br>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</li>`
|
</li>`
|
||||||
} else {
|
} else {
|
||||||
return `<li value=${node?.value}>${childrenText}</li>`
|
return `<li ${hasSubLists ? `class="nestedListItem" ` : ''}value=${node?.value}>${childrenText}</li>`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nodeTypes: [ListItemNode.getType()],
|
nodeTypes: [ListItemNode.getType()],
|
||||||
|
|||||||
@@ -331,6 +331,10 @@
|
|||||||
list-style-position: inside;
|
list-style-position: inside;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__ul ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__listItem {
|
&__listItem {
|
||||||
margin: 0 0px 0.4em 16px;
|
margin: 0 0px 0.4em 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user