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.
|
||||
|
||||
#### 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
|
||||
|
||||
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 { Field, RichTextField, TextField } from 'payload/types'
|
||||
import type { Field, RichTextField } from 'payload/types'
|
||||
|
||||
import type { AdapterProps, LexicalRichTextAdapter } from '../../../../../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'
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* Whether the lexicalHTML field should be hidden in the admin panel
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
hidden?: boolean
|
||||
name: string
|
||||
}
|
||||
|
||||
@@ -53,13 +59,16 @@ export const lexicalHTML: (
|
||||
**/
|
||||
lexicalFieldName: string,
|
||||
props: Props,
|
||||
) => TextField = (lexicalFieldName, props) => {
|
||||
const { name = 'lexicalHTML' } = props
|
||||
) => Field = (lexicalFieldName, props) => {
|
||||
const { name = 'lexicalHTML', hidden = true } = props
|
||||
return {
|
||||
name,
|
||||
type: 'text',
|
||||
type: 'code',
|
||||
admin: {
|
||||
hidden: true,
|
||||
editorOptions: {
|
||||
language: 'html',
|
||||
},
|
||||
hidden,
|
||||
},
|
||||
hooks: {
|
||||
afterRead: [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SerializedListItemNode, SerializedListNode } from '@lexical/list'
|
||||
|
||||
import lexicalListImport from '@lexical/list'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
const { ListItemNode, ListNode } = lexicalListImport
|
||||
|
||||
import type { HTMLConverter } from '../converters/html/converter/types.js'
|
||||
@@ -19,13 +20,15 @@ export const ListHTMLConverter: HTMLConverter<SerializedListNode> = {
|
||||
payload,
|
||||
})
|
||||
|
||||
return `<${node?.tag} class="${node?.listType}">${childrenText}</${node?.tag}>`
|
||||
return `<${node?.tag} class="list-${node?.listType}">${childrenText}</${node?.tag}>`
|
||||
},
|
||||
nodeTypes: [ListNode.getType()],
|
||||
}
|
||||
|
||||
export const ListItemHTMLConverter: HTMLConverter<SerializedListItemNode> = {
|
||||
converter: async ({ converters, node, parent, payload }) => {
|
||||
const hasSubLists = node.children.some((child) => child.type === 'list')
|
||||
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
lexicalNodes: node.children,
|
||||
@@ -37,19 +40,30 @@ export const ListItemHTMLConverter: HTMLConverter<SerializedListItemNode> = {
|
||||
})
|
||||
|
||||
if ('listType' in parent && parent?.listType === 'check') {
|
||||
const uuid = uuidv4()
|
||||
|
||||
return `<li aria-checked=${node.checked ? 'true' : 'false'} class="${
|
||||
'list-item-checkbox' + node.checked
|
||||
? 'list-item-checkbox-checked'
|
||||
: 'list-item-checkbox-unchecked'
|
||||
'list-item-checkbox' +
|
||||
(node.checked ? ' list-item-checkbox-checked' : ' list-item-checkbox-unchecked') +
|
||||
(hasSubLists ? ' nestedListItem' : '')
|
||||
}"
|
||||
role="checkbox"
|
||||
tabIndex=${-1}
|
||||
value=${node?.value}
|
||||
>
|
||||
${childrenText}
|
||||
${
|
||||
hasSubLists
|
||||
? childrenText
|
||||
: `
|
||||
<input type="checkbox" id="${uuid}"${node.checked ? ' checked' : ''}>
|
||||
<label for="${uuid}">${childrenText}</label><br>
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
</li>`
|
||||
} else {
|
||||
return `<li value=${node?.value}>${childrenText}</li>`
|
||||
return `<li ${hasSubLists ? `class="nestedListItem" ` : ''}value=${node?.value}>${childrenText}</li>`
|
||||
}
|
||||
},
|
||||
nodeTypes: [ListItemNode.getType()],
|
||||
|
||||
@@ -331,6 +331,10 @@
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
&__ul ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__listItem {
|
||||
margin: 0 0px 0.4em 16px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user