#Custom Lexical Paragraph feature

30 messages · Page 1 of 1 (latest)

wise fulcrum
#

Hey, I want to create my own Lexical Paragraph, that will render on my frontend with a custom class added to the p Tag. I had a working example, but it broke out of nothing and I can't get it to work again. I always get an error like:

Unable to read undefined reading clone/get type.

Does anyone have a working payload v3 example of a custom element node feature and would explain to me what I need to do? Or does anyone maybe have an Idea how to fix the error?

plain kelp
#

Could you just use child css classes

#
my-class > p {
 margin: 2px
}```
zenith cloak
#

Share the code and we can take a look.

wise fulcrum
wise fulcrum
# zenith cloak Share the code and we can take a look.

Okay I have two approaches:

  1. I found this Issue on lexical GitHub
    https://github.com/facebook/lexical/issues/5867

So I created this txs file:

import { $applyNodeReplacement, LexicalNode, ParagraphNode } from '@payloadcms/richtext-lexical/lexical'

export class ParagraphLargeNode extends ParagraphNode {
  static getType() {
    return 'ParagraphLarge'
  }

  static clone(node) {
    return new ParagraphLargeNode(node.__key)
  }
  createDOM(config) {
    const element = super.createDOM(config)
    element.classList.add('text-body-large')
    return element
  }
}

export function $createParagraphLargeNode(): ParagraphLargeNode {
  return $applyNodeReplacement(new ParagraphLargeNode())
}

export function $isParagraphLargeNode(node: LexicalNode | null | undefined): node is ParagraphLargeNode {
  return node instanceof ParagraphNode
}
GitHub

The ParagraphNode is hard core in lexical, and called in selection related function directly. If we want to customize the ParagraphNode just like change the HTML it create, it will be difficult. Th...

#
export type SerializedParagraphLargeNode = Spread<
  {
    textFormat: number
    textStyle: string
  },
  SerializedElementNode
>

export class ParagraphLargeNode extends ElementNode {
  ['constructor']!: KlassConstructor<typeof ParagraphLargeNode>
  /** @internal */
  __textFormat: number
  __textStyle: string

  constructor(key?: NodeKey) {
    super(key)
    this.__textFormat = 0
    this.__textStyle = ''
  }

  static getType(): string {
    return 'ParagraphLarge'
  }

  getTextFormat(): number {
    const self = this.getLatest()
    return self.__textFormat
  }

  getTextStyle(): string {
    const self = this.getLatest()
    return self.__textStyle
  }

  static clone(node: ParagraphLargeNode): ParagraphLargeNode {
    return new ParagraphLargeNode(node.__key)
  }

  afterCloneFrom(prevNode: this) {
    super.afterCloneFrom(prevNode)
    this.__textFormat = prevNode.__textFormat
    this.__textStyle = prevNode.__textStyle
  }

  createDOM(): HTMLElement {
    const dom = document.createElement('p')
    addClassNamesToElement(dom, 'LexicalEditorTheme__paragraph')
    dom.style.fontSize = '1.25rem'
    return dom
  }

  updateDOM(): boolean {
    return false
  }

  static importDOM(): DOMConversionMap | null {
    return {
      div: () => ({
        conversion: $ParagraphLargeNodeConverter,
        priority: 0,
      }),
    }
  }

  // next message
}
#
export class ParagraphLargeNode extens ElementNode {
  // Previous Message

   exportDOM(): DOMExportOutput {
    return { element: document.createElement('div') }
  }

  static importJSON(): ParagraphLargeNode {
    return $createParagraphLargeNode()
  }

  exportJSON(): SerializedParagraphLargeNode {
    return {
      ...super.exportJSON(),
      textFormat: this.getTextFormat(),
      textStyle: this.getTextStyle(),
      type: 'ParagraphLarge',
      version: 1,
    }
  }

  isInline(): false {
    return false
  }

  insertNewAfter(_: RangeSelection, restoreSelection?: boolean): ParagraphNode {
    const newBlock = $createParagraphNode()
    const direction = this.getDirection()
    newBlock.setDirection(direction)
    this.insertAfter(newBlock, restoreSelection)
    return newBlock
  }
}

export function $ParagraphLargeNodeConverter(): DOMConversionOutput {
  return { node: $createParagraphLargeNode() }
}

export function $createParagraphLargeNode(): ParagraphLargeNode {
  return new ParagraphLargeNode()
}

export function $isParagraphLargeNode(node: LexicalNode | null | undefined): node is ParagraphLargeNode {
  return node instanceof ParagraphLargeNode
}
#

I Import from:

from '@payloadcms/richtext-lexical/lexical'
from '@payloadcms/richtext-lexical/lexical/utils'

#

Got this new Error 😄

zenith cloak
#

Whats your createServerFeature and createClientFeature look like

wise fulcrum
#

One second

#
'use client'

import { createClientFeature, toolbarTextDropdownGroupWithItems } from '@payloadcms/richtext-lexical/client'
import { $getSelection, $isRangeSelection } from '@payloadcms/richtext-lexical/lexical'
import { $setBlocksType } from '@payloadcms/richtext-lexical/lexical/selection'

import {
  $createParagraphLargeNode,
  $isParagraphLargeNode,
  ParagraphLargeNode,
} from '@/lexical/ParagraphLarge/nodes/PepParagraphLargeNode'

export const ParagraphLargeFeatureClient = createClientFeature({
  toolbarFixed: {
    groups: [
      toolbarTextDropdownGroupWithItems([
        {
          isActive: ({ selection }) => {
            if (!$isRangeSelection(selection)) {
              return false
            }
            for (const node of selection.getNodes()) {
              if (!$isParagraphLargeNode(node) && !$isParagraphLargeNode(node.getParent())) {
                return false
              }
            }
            return true
          },
          onSelect: ({ editor }) => {
            editor.update(() => {
              const selection = $getSelection()
              $setBlocksType(selection, () => $createParagraphLargeNode())
            })
          },
          label: 'Large Text',
          key: 'ParagraphLarge',
        },
      ]),
    ],
  },
  nodes: [ParagraphLargeNode],
})
#
import { createServerFeature } from '@payloadcms/richtext-lexical'

export const ParagraphLargeFeature = createServerFeature({
  key: 'ParagraphLarge',
  feature: {
    ClientFeature: '@/lexical/ParagraphLarge/feature.client#ParagraphLargeFeatureClient',
  },
})
#

Why does this need to be this hard 😄 I wasted so much time writing this class like 4 times xD

zenith cloak
#

Still looking through it but one inconsistency I can see is your keys have some capital letters in them. It should be paragraphlarge to match other examples.

wise fulcrum
zenith cloak
#

Sorry I mean the custom node getType (and wherever else that is used)

wise fulcrum
zenith cloak
#

You’re also not using camelCase for your keys

#

ParagraphLarge should be paragraphLarge

wise fulcrum
#

Oh yeah thats only because i removed the prefix

#

in my code there is ...ParagraphLarge

zenith cloak
#

Your createParagraphLargeNode function doesn’t look right, it’s missing applyNodeReplacement

wise fulcrum
#

I also added it but no changes

zenith cloak
#

If you haven’t figured it out by tomorrow I’ll get on my PC and create it and see where the problem lies.

wise fulcrum
odd lichen
#

Hey, I want to create my own Lexical Paragraph, that will render on my frontend with a custom class added to the p Tag
Just customize the lexical => JSX/HTML converter. Creating your own lexical node is overkill, and the node replacement API is not fun to use.

mighty cobalt
#

Hey, I'm in a similar situation and need to customize the JSX/HTML converter. Can anyone point me to an example how to do this?