Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

(Lexical) Extending Blocks feature breaks context

default discord avatar
ingrid741last year
16

Extending Payload's

BlocksFeatureClient

to override the plugin, the command dispatch for

INSERT_BLOCK_COMMAND

fails (even with identical plugin):



import { BlocksFeatureClient as BlocksFeatureClientPayload } from '@payloadcms/richtext-lexical/client'
import { BlocksPlugin } from './plugins/BlocksPlugin' // copypaste of original 

export const BlocksFeatureClient = createClientFeature(args => {

  const clientFeaturePayload = BlocksFeatureClientPayload(args)

  const feature =
    typeof clientFeaturePayload.feature === 'function'
      ? clientFeaturePayload.feature(args)
      : clientFeaturePayload.feature

  return {
    ...feature,
    plugins: [
      {
        Component: BlocksPlugin,
        position: 'normal',
      }  
    ]
  }
})


Odd thing is returning just the feature like so works:



return {
    ...feature
}


Suggesting that merely extending it what's breaking it.



Debugging we find that


- The returned feature seems well formed, identical to the unaltered feature.


- The

slashMenu

property on the returned feature (where the

INSERT_BLOCK_COMMAND

is invoked) is preserved.


- The plugin is invoked, but:


- Breakpoints in the

editor.update()

invoked on

INSERT_BLOCK_COMMAND

are not hit.



Please help - stuck on this on the 3rd day 🙏



Hi

@360823574644129795

, could I ask you to take a look at this when you get a minute?

  • default discord avatar
    german.jablolast year
    import { BlocksPlugin } from './plugins/BlocksPlugin' // copypaste of original

    If you copied that file from the original, it probably means that you are using Lexical imports instead of

    @payloadcms/richtext-lexical/lexical/...

    , is that right?


    If so, try replacing the imports. It is likely that we are using a different version of Lexical than the one you have installed.

  • default discord avatar
    ingrid741last year

    I'm using the Payload proxies exactly as in the Payload plugin, no direct imports from lexical packages there



    My server* feature override FWIW:



    export const BlocksFeature = (props: BlocksFeatureProps): FeatureProviderServer => {
    
      const serverFeaturePayload = BlocksFeaturePayload({
        blocks: props.blocks,
        inlineBlocks: props.inlineBlocks
      })
    
      const feature = async (args: ServerFeatureProps<BlocksFeatureProps>) => {
        const featureResult =
          typeof serverFeaturePayload.feature === 'function'
            ? await serverFeaturePayload.feature(args)
            : serverFeaturePayload.feature
    
        return {
          ...featureResult,
          ClientFeature: '@/_lib/lexical/features/blocks/feature.client#BlocksFeatureClient',
        }
      }
    
      const featureProvider = createServerFeature<any, any, any>({
        ...serverFeaturePayload,
        feature: feature
      })
    
      return featureProvider()
    }


    What's interesting is the plugin override seems to work. I'm hitting breakpoints in it, just not in the listener for

    INSERT_BLOCK_COMMAND

    . It's like the listener is never registered.

  • discord user avatar
    alessiogr
    last year

    if you're using the same react & lexical versions, I don't know why it wouldn't work



    Can you show the code for your plugin?



    Ah and where are you getting the

    INSERT_BLOCK_COMMAND

    from?



    not sure how exactly lexical finds the correct listener for a dispatched command



    if it compares the object references instead of the command

    type

    string value, it might not work if you re-define that command

  • default discord avatar
    ingrid741last year

    My React versions are pinned to the recommended 19-rc and I don't have any lexical packages installed.



    The commands are just copied in verbatim,



    'use client'
    
    import { LexicalCommand, createCommand } from "@payloadcms/richtext-lexical/lexical"
    import { InsertBlockPayload } from "./BlocksPlugin"
    
    
    export const INSERT_BLOCK_COMMAND: LexicalCommand<InsertBlockPayload> =
      createCommand('INSERT_BLOCK_COMMAND')
    
    export const INSERT_INLINE_BLOCK_COMMAND: LexicalCommand<Partial<InsertBlockPayload>> =
      createCommand('INSERT_INLINE_BLOCK_COMMAND')


    Here's the plugin:



    'use client'
    
    import { formatDrawerSlug, useEditDepth } from '@payloadcms/ui'
    import { useEffect, useState } from 'react'
    import { INSERT_BLOCK_COMMAND, INSERT_INLINE_BLOCK_COMMAND } from './commands'
    import { BlockFields, PluginComponent } from '@payloadcms/richtext-lexical'
    import { useLexicalComposerContext } from '@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext'
    import { $createBlockNode, $createInlineBlockNode, $isInlineBlockNode, BlockNode, useEditorConfigContext, useLexicalDrawer } from '@payloadcms/richtext-lexical/client'
    import { $insertNodeToNearestRoot, $wrapNodeInElement, mergeRegister } from '@payloadcms/richtext-lexical/lexical/utils'
    
    import { 
      $createParagraphNode, 
      $getNodeByKey, 
      $getPreviousSelection, 
      $getSelection, 
      $insertNodes, 
      $isParagraphNode, 
      $isRangeSelection, 
      $isRootOrShadowRoot, 
      COMMAND_PRIORITY_EDITOR 
    } from '@payloadcms/richtext-lexical/lexical'
    
    import type { BlockFieldsOptionalID } from '../nodes/ServerBlockNode'
    
    
    export type InsertBlockPayload = BlockFieldsOptionalID
    
    export const BlocksPlugin: PluginComponent = () => {
      const [editor] = useLexicalComposerContext()
    
      const [targetNodeKey, setTargetNodeKey] = useState<null | string>(null)
    
      const { setCreatedInlineBlock, uuid } = useEditorConfigContext()
      const editDepth = useEditDepth()
    
      const drawerSlug = formatDrawerSlug({
        slug: `lexical-inlineBlocks-create-` + uuid,
        depth: editDepth,
      })
    
      const { toggleDrawer } = useLexicalDrawer(drawerSlug, true)
    
      useEffect(() => {
        if (!editor.hasNodes([BlockNode])) {
          throw new Error('BlocksPlugin: BlocksNode not registered on editor')
        }


        return mergeRegister(
          editor.registerCommand<InsertBlockPayload>(
            INSERT_BLOCK_COMMAND,
            (payload: InsertBlockPayload) => {
              editor.update(() => {
                const selection = $getSelection() || $getPreviousSelection()
    
                if ($isRangeSelection(selection)) {
                  const blockNode = $createBlockNode(payload)
                  // Insert blocks node BEFORE potentially removing focusNode, as $insertNodeToNearestRoot errors if the focusNode doesn't exist
                  $insertNodeToNearestRoot(blockNode)
    
                  const { focus } = selection
                  const focusNode = focus.getNode()
    
                  // First, delete currently selected node if it's an empty paragraph and if there are sufficient
                  // paragraph nodes (more than 1) left in the parent node, so that we don't "trap" the user
                  if (
                    $isParagraphNode(focusNode) &&
                    focusNode.getTextContentSize() === 0 &&
                    focusNode
                      .getParentOrThrow()
                      .getChildren()
                      .filter((node) => $isParagraphNode(node)).length > 1
                  ) {
                    focusNode.remove()
                  }
                }
              })
    
              return true
            },
            COMMAND_PRIORITY_EDITOR,
          ),
          ...
      return null
    }
  • discord user avatar
    alessiogr
    last year
    My React versions are pinned to the recommended 19-rc

    which one exactly?



    And you are defining the

    INSERT_BLOCK_COMMAND

    yourself in that

    commands

    file, right?



    I think that's why it doesn't work

  • default discord avatar
    ingrid741last year
        "react": "19.0.0-rc-66855b96-20241106",
        "react-dom": "19.0.0-rc-66855b96-20241106",
  • discord user avatar
    alessiogr
    last year

    just checked, and lexical checks for command reference equality - so the command that's dispatched by us will be a different one

  • default discord avatar
    ingrid741last year

    Ahh



    Explains it

  • discord user avatar
    alessiogr
    last year

    change those to just "19.0.0" - no need to use the rc versions anymore

  • default discord avatar
    ingrid741last year

    Oh okay thx

  • discord user avatar
    alessiogr
    last year

    I'll export those commands for you rn

  • default discord avatar
    ingrid741last year

    Thank you so much

  • discord user avatar
    alessiogr
    last year

    you're welcome!

  • default discord avatar
    ingrid741last year

    Super appreciated!

  • discord user avatar
    alessiogr
    last year
    @735059082293149777

    here's a release you can use with the commands exported from

    @967091941873426493

    /richtext-lexical/client:

    3.24.0-canary.f229d8d
  • default discord avatar
    ingrid741last year

    Yep I got it, thanks 😊



    I think version mismatch could be the cause of nullref errors showing up, console prints these: Error: Mismatching "payload" dependency versions found: @payloadcms/richtext-lexical@3.24.0-canary.f229d8d (Please change this to 3.23.0)

    Just go in your package.json and set all payload packages to the same version, reinstall

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get dedicated engineering support directly from the Payload team.