payload icon indicating copy to clipboard operation
payload copied to clipboard

Lexical editor forcibly in readonly mode

Open stuckinsnow opened this issue 1 year ago • 11 comments

Describe the Bug

Lexical editor is in readonly mode, the toolbar at the top doesn't show, and it is not sparking the joy I had assumed it would spark. I don't have any type errors anywhere, no other errors. Types build correctly, import map works, etc.

Link to the code that reproduces this issue

NA

Reproduction Steps

  1. Made a blank template, using v127
  2. Put lexical inside of tabs
  3. Updated to 3.0.1

Which area(s) are affected? (Select all that apply)

area: core

Environment Info

NA

stuckinsnow avatar Nov 19 '24 21:11 stuckinsnow

Please add a reproduction in order for us to be able to investigate.

Depending on the quality of reproduction steps, this issue may be closed if no reproduction is provided.

Why was this issue marked with the invalid-reproduction label?

To be able to investigate, we need access to a reproduction to identify what triggered the issue. We prefer a link to a public GitHub repository created with create-payload-app@beta -t blank or a forked/branched version of this repository with tests added (more info in the reproduction-guide).

To make sure the issue is resolved as quickly as possible, please make sure that the reproduction is as minimal as possible. This means that you should remove unnecessary code, files, and dependencies that do not contribute to the issue. Ensure your reproduction does not depend on secrets, 3rd party registries, private dependencies, or any other data that cannot be made public. Avoid a reproduction including a whole monorepo (unless relevant to the issue). The easier it is to reproduce the issue, the quicker we can help.

Please test your reproduction against the latest version of Payload to make sure your issue has not already been fixed.

I added a link, why was it still marked?

Ensure the link is pointing to a codebase that is accessible (e.g. not a private repository). "example.com", "n/a", "will add later", etc. are not acceptable links -- we need to see a public codebase. See the above section for accepted links.

Useful Resources

github-actions[bot] avatar Nov 19 '24 21:11 github-actions[bot]

I have the same exact issue 3.0.1 - I followed all the upgrades from each beta and never had this problem until the stable release.

bowendevteam avatar Nov 19 '24 21:11 bowendevteam

I'm having the exact same issue. Some instances of the lexical editor, with the exact same config yet in different collections are greyed out / read-only state.

The affected instances is occurring within tabs. I'm importing a hero (which is a group) and that is used within the tabs.

Nightmare as it now in prod and the editors are stuck.

Image Image

jakehopking avatar Nov 19 '24 21:11 jakehopking

Another issue, also within tabs is relationship selects are randomly blank.

You can see reusableContent is working in some places, and not others.

Image

Image

jakehopking avatar Nov 19 '24 22:11 jakehopking

to provide some more context here, I did some testing and it appears as though beta.132 broke this. I downgrade to beta.131 and the lexical editors are no longer read only mode. Also a ton of other bugs seemed to be resolved

bowendevteam avatar Nov 19 '24 22:11 bowendevteam

I have done the same as @bowendevteam. ~Downgrading to 131 has solved the lexical editor readonly issue.~ Nope... the issue still persists in 131. I have just found another area where it's affected.

Image

The relationship issue also still persists, so must have been introduced in the migration script...?

jakehopking avatar Nov 19 '24 22:11 jakehopking

in >= 131 there are layout blocks that are now blank / unpopulated since the migration... again within tabs

Image

jakehopking avatar Nov 19 '24 22:11 jakehopking

Hey all, we are actively looking into this. @jakehopking — are your tabs named, or unnamed? Can you provide us with an exact structure of fields where your rich text editor is either blank or rendering as read-only?

We tried reproducing with this:

  1. Made a blank template, using v127
  2. Put lexical inside of tabs
  3. Updated to 3.0.1

But 3.0.1 works as expected. @stuckinsnow is it possible that you might have older versions of packages in your node_modules still? Or, maybe Next.js has cached older versions?

Try and delete your .next folder and restart the server. It's possible that you might have a cached build of Payload in your .next folder.

Note that this was indeed a bug up to beta 132: https://github.com/payloadcms/payload/pull/9237

We could reproduce it and it was resolved, but if there are more remaining cases to catch, we will do so immediately.

Keep us updated!

jmikrut avatar Nov 19 '24 22:11 jmikrut

Here's an example of one collection where the lexical editors are broken. Worth noting that in some other collections, VERY similar patterns don't produce a broken

heroArticle.ts

import type { Field } from 'payload'

import {
  FixedToolbarFeature,
  HeadingFeature,
  InlineToolbarFeature,
  lexicalEditor,
} from '@payloadcms/richtext-lexical'

export const heroArticle: Field = {
  name: 'hero',
  type: 'group',
  fields: [
    {
      name: 'type',
      type: 'select',
      defaultValue: 'articleHorizontal',
      label: 'Type',
      options: [
        {
          label: 'Article Vertical',
          value: 'articleVertical',
        },
        {
          label: 'Article Horizontal',
          value: 'articleHorizontal',
        },
      ],
      admin: {
        isClearable: false,
      },
      required: true,
    },
    {
      name: 'richTextHeading',
      type: 'richText',
      label: 'Heading',
      required: true,
      admin: {
        description: 'The heading that will be displayed in the hero section.',
      },
      editor: lexicalEditor({
        features: ({ rootFeatures }) => {
          return [
            ...rootFeatures,
            HeadingFeature({ enabledHeadingSizes: ['h1'] }),
            FixedToolbarFeature(),
            InlineToolbarFeature(),
          ]
        },
      }),
    },
    {
      name: 'richTextIntro',
      type: 'richText',
      label: 'Intro',
      required: true,
      editor: lexicalEditor({
        features: ({ rootFeatures }) => {
          return [
            ...rootFeatures,
            // HeadingFeature({ enabledHeadingSizes: [] }),
            FixedToolbarFeature(),
            InlineToolbarFeature(),
          ]
        },
      }),
    },
    {
      name: 'media',
      type: 'upload',
      admin: {
        description:
          'The image that will be displayed in the hero section. Note: To specify an images to use within carousels, cards, and SEO, see the `Carousel`, `Card`, and `SEO` tabs respectively.',
      },
      relationTo: 'media',
      required: true,
    },
    {
      name: 'showCaption',
      type: 'checkbox',
      defaultValue: false,
      label: 'Show Media Caption',
      admin: {
        description: 'Show the caption for the media. Note: Caption is set in the Media Library.',
      },
    },
  ],
  label: false,
}

is imported into article index.ts

import type { CollectionConfig } from 'payload'
import { authenticated } from '@payload/access/authenticated'
import { authenticatedOrPublished } from '@payload/access/authenticatedOrPublished'
import { slugField } from '@payload/fields/slug'
import { heroArticle } from '@payload/fields/heroArticle'
import { publishedAt } from '@payload/fields/publishedAt'
import { collectionNameField } from '@payload/fields/collectionName'
import { populatePublishedAt } from '@payload/hooks/populatePublishedAt'
import { generatePreviewPath } from '@payload/utilities/generatePreviewPath'
import { CollectionSlugs } from '@tokens/slugs'
import { revalidateArticle } from './hooks/revalidateArticle'
import { themeField } from '@payload/fields/theme'
import { categoriesField } from '@payload/fields/categories'
import { tagsField } from '@payload/fields/tags'
import { authorsField } from '@payload/fields/authors'
import { populateAuthors } from '@payload/hooks/populateAuthors'
import { ArticleMediaBlock } from '@payload/blocks/ArticleMediaBlock'
import { ArticleQuoteBlock } from '@payload/blocks/ArticleQuoteBlock'
import { ArticleButtonBlock } from '@payload/blocks/ArticleButtonBlock'
import { FormBlock } from '@payload/blocks/Form'
import { ReusableContent } from '@payload/blocks/ReusableContent'
import { LinksGridBlock } from '@payload/blocks/LinksGrid'
import { commonTabsCardFields, commonTabsCarouselFields } from '@payload/fields/commonTabs'
import {
  BlocksFeature,
  FixedToolbarFeature,
  HeadingFeature,
  HorizontalRuleFeature,
  InlineToolbarFeature,
  lexicalEditor,
} from '@payloadcms/richtext-lexical'
import {
  MetaDescriptionField,
  MetaImageField,
  MetaTitleField,
  OverviewField,
  PreviewField,
} from '@payloadcms/plugin-seo/fields'
import { revalidateCollectionAndSlug } from '@payload/hooks/revalidateCollectionAndSlug'
import { ArticleVideoBlock } from '@payload/blocks/ArticleVideoBlock'
import { deleteCollectionAndSlug } from '@payload/hooks/deleteCollectionAndSlug'
import { deleteArticle } from './hooks/deleteArticle'

export const Articles: CollectionConfig = {
  slug: CollectionSlugs.articles,
  access: {
    create: authenticated,
    delete: authenticated,
    read: authenticatedOrPublished,
    update: authenticated,
  },
  admin: {
    useAsTitle: 'title',
    defaultColumns: ['title', 'slug', 'publishedAt', 'updatedAt'],
    livePreview: {
      url: ({ data }) => {
        const path = generatePreviewPath({
          // path: `${RouterPaths.articles}${typeof data?.slug === 'string' ? data.slug : ''}`,
          slug: `${typeof data?.slug === 'string' ? data.slug : ''}`,
          collection: 'articles',
        })
        return `${process.env.NEXT_PUBLIC_SERVER_URL}${path}`
      },
    },
    preview: (data) =>
      generatePreviewPath({
        slug: `${typeof data?.slug === 'string' ? data.slug : ''}`,
        collection: 'articles',
      }),
  },
  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    },
    {
      name: 'description',
      type: 'textarea',
      required: true,
      admin: {
        description:
          'This description, or abstract, is used (unless overridden) for carousel descriptions, card descriptions, and SEO descriptions. To override the aforementioned descriptions, see the `Carousel`, `Card`, and `SEO` tabs respectively.',
      },
    },
    {
      type: 'tabs',
      tabs: [
        {
          label: 'Hero',
          fields: [heroArticle],
        },
        {
          label: 'Content',
          fields: [
            {
              name: 'content',
              type: 'richText',
              editor: lexicalEditor({
                features: ({ rootFeatures }) => {
                  return [
                    ...rootFeatures,
                    HeadingFeature({ enabledHeadingSizes: ['h2', 'h3', 'h4', 'h5'] }),
                    BlocksFeature({
                      blocks: [
                        ArticleMediaBlock,
                        ArticleVideoBlock,
                        ArticleQuoteBlock,
                        ArticleButtonBlock,
                        FormBlock,
                      ],
                    }),
                    FixedToolbarFeature(),
                    InlineToolbarFeature(),
                    HorizontalRuleFeature(),
                  ]
                },
              }),
              label: false,
              required: true,
            },
          ],
        },
        {
          label: 'Blocks',
          fields: [
            {
              name: 'layout',
              type: 'blocks',
              blocks: [ReusableContent, LinksGridBlock],
              admin: {
                description:
                  'Additional blocks can be added here. These are placed at the bottom of the article.',
              },
            },
          ],
        },
        {
          label: 'Carousel',
          description: 'This tab is used to set how this page appears when used within carousels.',
          fields: [...commonTabsCarouselFields],
        },
        {
          label: 'Card',
          description: 'This tab is used to set how this page appears when used within cards.',
          fields: [...commonTabsCardFields],
        },
        {
          label: 'Meta',
          description: "This tab is used to set the article's meta settings.",
          fields: [
            {
              name: 'relatedArticles',
              type: 'relationship',
              admin: {
                position: 'sidebar',
              },
              filterOptions: ({ id }) => {
                return {
                  id: {
                    not_in: [id],
                  },
                }
              },
              hasMany: true,
              relationTo: CollectionSlugs.articles,
            },
            categoriesField,
            tagsField,
            themeField({}),
          ],
        },
        {
          label: 'SEO',
          name: 'meta',
          fields: [
            OverviewField({
              titlePath: 'meta.title',
              descriptionPath: 'meta.description',
              imagePath: 'meta.image',
            }),
            MetaTitleField({
              hasGenerateFn: true,
            }),
            MetaImageField({
              relationTo: 'media',
            }),

            MetaDescriptionField({}),
            PreviewField({
              // if the `generateUrl` function is configured
              hasGenerateFn: true,

              // field paths to match the target field for data
              titlePath: 'meta.title',
              descriptionPath: 'meta.description',
            }),
          ],
        },
      ],
    },
    publishedAt,
    slugField(),
    authorsField,
    // This field is only used to populate the user data via the `populateAuthors` hook
    // This is because the `user` collection has access control locked to protect user privacy
    // GraphQL will also not return mutated user data that differs from the underlying schema
    {
      name: 'populatedAuthors',
      type: 'array',
      access: {
        update: () => false,
      },
      admin: {
        disabled: true,
        readOnly: true,
      },
      fields: [
        {
          name: 'id',
          type: 'text',
        },
        {
          name: 'name',
          type: 'text',
        },
      ],
    },
    collectionNameField(CollectionSlugs.articles),
  ],
  hooks: {
    afterChange: [revalidateCollectionAndSlug, revalidateArticle],
    afterDelete: [deleteCollectionAndSlug, deleteArticle],
    afterRead: [populateAuthors],
    beforeChange: [populatePublishedAt],
  },
  versions: {
    drafts: {
      autosave: {
        interval: 100, // We set this interval for optimal live preview
      },
    },
    maxPerDoc: 50,
  },
}

Produces this:

Image

jakehopking avatar Nov 19 '24 22:11 jakehopking

Issue fixed thanks to Philip!

      name: 'populatedAuthors',
      type: 'array',
      access: {
        update: () => false, // <--- change this to true until a fix is released
      },

https://discord.com/channels/967097582721572934/1308565576104546458/1308570040748081262

jakehopking avatar Nov 19 '24 23:11 jakehopking

Fix incoming first thing tomorrow - thank you all for helping reproduce!

jmikrut avatar Nov 20 '24 03:11 jmikrut

🚀 This is included in version v3.0.2

github-actions[bot] avatar Nov 20 '24 21:11 github-actions[bot]

This issue has been automatically locked. Please open a new issue if this issue persists with any additional detail.

github-actions[bot] avatar Nov 22 '24 04:11 github-actions[bot]