feat(richtext-lexical)!: rework how population works and saves data, improve node typing
This commit is contained in:
@@ -33,9 +33,15 @@ let serverURL: string
|
||||
/**
|
||||
* Client-side navigation to the lexical editor from list view
|
||||
*/
|
||||
async function navigateToLexicalFields(navigateToListView: boolean = true) {
|
||||
async function navigateToLexicalFields(
|
||||
navigateToListView: boolean = true,
|
||||
localized: boolean = false,
|
||||
) {
|
||||
if (navigateToListView) {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'lexical-fields')
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(
|
||||
serverURL,
|
||||
localized ? 'lexical-localized-fields' : 'lexical-fields',
|
||||
)
|
||||
await page.goto(url.list)
|
||||
}
|
||||
|
||||
@@ -1101,7 +1107,7 @@ describe('lexical', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('should respect required error state in deeply nested text field', async () => {
|
||||
test('should respect required error state in deeply nested text field', async () => {
|
||||
await navigateToLexicalFields()
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
@@ -1117,12 +1123,18 @@ describe('lexical', () => {
|
||||
await page.click('#action-save', { delay: 100 })
|
||||
await expect(page.locator('.Toastify')).toContainText('The following field is invalid')
|
||||
|
||||
const requiredTooltip = conditionalArrayBlock
|
||||
.locator('.tooltip-content:has-text("This field is required.")')
|
||||
.first()
|
||||
await requiredTooltip.scrollIntoViewIfNeeded()
|
||||
// Check if error is shown next to field
|
||||
await expect(
|
||||
conditionalArrayBlock
|
||||
.locator('.tooltip-content:has-text("This field is required.")')
|
||||
.first(),
|
||||
).toBeVisible()
|
||||
await expect(requiredTooltip).toBeInViewport() // toBeVisible() doesn't work for some reason
|
||||
})
|
||||
})
|
||||
|
||||
describe('localization', () => {
|
||||
test.skip('ensure simple localized lexical field works', async () => {
|
||||
await navigateToLexicalFields(true, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'upload',
|
||||
version: 1,
|
||||
version: 2,
|
||||
fields: {
|
||||
caption: {
|
||||
root: {
|
||||
@@ -57,11 +57,9 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 1,
|
||||
version: 2,
|
||||
relationTo: 'text-fields',
|
||||
value: {
|
||||
id: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
@@ -69,9 +67,7 @@ export function generateLexicalRichText() {
|
||||
},
|
||||
},
|
||||
relationTo: 'uploads',
|
||||
value: {
|
||||
id: '{{UPLOAD_DOC_ID}}',
|
||||
},
|
||||
value: '{{UPLOAD_DOC_ID}}',
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
@@ -120,11 +116,9 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 1,
|
||||
version: 2,
|
||||
relationTo: 'rich-text-fields',
|
||||
value: {
|
||||
id: '{{RICH_TEXT_DOC_ID}}',
|
||||
},
|
||||
value: '{{RICH_TEXT_DOC_ID}}',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
@@ -173,11 +167,9 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 1,
|
||||
version: 2,
|
||||
relationTo: 'text-fields',
|
||||
value: {
|
||||
id: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
|
||||
95
test/fields/collections/LexicalLocalized/index.ts
Normal file
95
test/fields/collections/LexicalLocalized/index.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
import { BlocksFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { lexicalLocalizedFieldsSlug } from '../../slugs.js'
|
||||
|
||||
export const LexicalLocalizedFields: CollectionConfig = {
|
||||
slug: lexicalLocalizedFieldsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
listSearchableFields: ['title'],
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'lexicalSimple',
|
||||
type: 'richText',
|
||||
localized: true,
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [...defaultFeatures],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'lexicalBlocksLocalized',
|
||||
admin: {
|
||||
description: 'Localized field with localized block subfields',
|
||||
},
|
||||
type: 'richText',
|
||||
localized: true,
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
{
|
||||
slug: 'block',
|
||||
fields: [
|
||||
{
|
||||
name: 'textLocalized',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'rel',
|
||||
type: 'relationship',
|
||||
relationTo: lexicalLocalizedFieldsSlug,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'lexicalBlocksSubLocalized',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
description: 'Non-localized field with localized block subfields',
|
||||
},
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
{
|
||||
slug: 'block',
|
||||
fields: [
|
||||
{
|
||||
name: 'textLocalized',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'rel',
|
||||
type: 'relationship',
|
||||
relationTo: lexicalLocalizedFieldsSlug,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { SerializedRelationshipNode } from '@payloadcms/richtext-lexical'
|
||||
import type { SerializedEditorState, SerializedParagraphNode, SerializedTextNode } from 'lexical'
|
||||
|
||||
import { lexicalLocalizedFieldsSlug } from '../../slugs.js'
|
||||
|
||||
export function textToLexicalJSON({
|
||||
text,
|
||||
lexicalLocalizedRelID,
|
||||
}: {
|
||||
lexicalLocalizedRelID?: number | string
|
||||
text: string
|
||||
}) {
|
||||
const editorJSON: SerializedEditorState = {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
direction: 'ltr',
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text,
|
||||
type: 'text',
|
||||
version: 1,
|
||||
} as SerializedTextNode,
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
} as SerializedParagraphNode,
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
if (lexicalLocalizedRelID) {
|
||||
editorJSON.root.children.push({
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 2,
|
||||
relationTo: lexicalLocalizedFieldsSlug,
|
||||
value: lexicalLocalizedRelID,
|
||||
} as SerializedRelationshipNode)
|
||||
}
|
||||
|
||||
return editorJSON
|
||||
}
|
||||
@@ -116,10 +116,8 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 1,
|
||||
value: {
|
||||
id: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
version: 2,
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
relationTo: 'text-fields',
|
||||
},
|
||||
{
|
||||
@@ -230,11 +228,9 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'upload',
|
||||
version: 1,
|
||||
version: 2,
|
||||
relationTo: 'uploads',
|
||||
value: {
|
||||
id: '{{UPLOAD_DOC_ID}}',
|
||||
},
|
||||
value: '{{UPLOAD_DOC_ID}}',
|
||||
fields: {
|
||||
caption: {
|
||||
root: {
|
||||
|
||||
@@ -12,6 +12,7 @@ import GroupFields from './collections/Group/index.js'
|
||||
import IndexedFields from './collections/Indexed/index.js'
|
||||
import JSONFields from './collections/JSON/index.js'
|
||||
import { LexicalFields } from './collections/Lexical/index.js'
|
||||
import { LexicalLocalizedFields } from './collections/LexicalLocalized/index.js'
|
||||
import { LexicalMigrateFields } from './collections/LexicalMigrate/index.js'
|
||||
import NumberFields from './collections/Number/index.js'
|
||||
import PointFields from './collections/Point/index.js'
|
||||
@@ -31,6 +32,7 @@ import { clearAndSeedEverything } from './seed.js'
|
||||
export const collectionSlugs: CollectionConfig[] = [
|
||||
LexicalFields,
|
||||
LexicalMigrateFields,
|
||||
LexicalLocalizedFields,
|
||||
{
|
||||
slug: 'users',
|
||||
admin: {
|
||||
|
||||
@@ -416,9 +416,10 @@ describe('Lexical', () => {
|
||||
/**
|
||||
* Depth 1 population:
|
||||
*/
|
||||
expect(subEditorRelationshipNode.value.id).toStrictEqual(createdRichTextDocID)
|
||||
expect(subEditorRelationshipNode.value).toStrictEqual(createdRichTextDocID)
|
||||
// But the value should not be populated and only have the id field:
|
||||
expect(Object.keys(subEditorRelationshipNode.value)).toHaveLength(1)
|
||||
|
||||
expect(typeof subEditorRelationshipNode.value).not.toStrictEqual('object')
|
||||
})
|
||||
|
||||
it('should populate relationship nodes inside of a sub-editor from a blocks node with 1 depth', async () => {
|
||||
@@ -463,9 +464,9 @@ describe('Lexical', () => {
|
||||
/**
|
||||
* Depth 2 population:
|
||||
*/
|
||||
expect(populatedDocEditorRelationshipNode.value.id).toStrictEqual(createdTextDocID)
|
||||
expect(populatedDocEditorRelationshipNode.value).toStrictEqual(createdTextDocID)
|
||||
// But the value should not be populated and only have the id field - that's because it would require a depth of 2
|
||||
expect(Object.keys(populatedDocEditorRelationshipNode.value)).toHaveLength(1)
|
||||
expect(populatedDocEditorRelationshipNode.value).not.toStrictEqual('object')
|
||||
})
|
||||
|
||||
it('should populate relationship nodes inside of a sub-editor from a blocks node with depth 2', async () => {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { dateDoc } from './collections/Date/shared.js'
|
||||
import { groupDoc } from './collections/Group/shared.js'
|
||||
import { jsonDoc } from './collections/JSON/shared.js'
|
||||
import { lexicalDocData } from './collections/Lexical/data.js'
|
||||
import { textToLexicalJSON } from './collections/LexicalLocalized/textToLexicalJSON.js'
|
||||
import { lexicalMigrateDocData } from './collections/LexicalMigrate/data.js'
|
||||
import { numberDoc } from './collections/Number/shared.js'
|
||||
import { pointDoc } from './collections/Point/shared.js'
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
groupFieldsSlug,
|
||||
jsonFieldsSlug,
|
||||
lexicalFieldsSlug,
|
||||
lexicalLocalizedFieldsSlug,
|
||||
lexicalMigrateFieldsSlug,
|
||||
numberFieldsSlug,
|
||||
pointFieldsSlug,
|
||||
@@ -49,7 +51,7 @@ import {
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export const seed = async (_payload) => {
|
||||
export const seed = async (_payload: Payload) => {
|
||||
if (_payload.db.name === 'mongoose') {
|
||||
await Promise.all(
|
||||
_payload.config.collections.map(async (coll) => {
|
||||
@@ -274,6 +276,74 @@ export const seed = async (_payload) => {
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
const lexicalLocalizedDoc1 = await _payload.create({
|
||||
collection: lexicalLocalizedFieldsSlug,
|
||||
data: {
|
||||
title: 'Localized Lexical en',
|
||||
lexicalSimple: textToLexicalJSON({ text: 'English text' }),
|
||||
lexicalBlocksLocalized: textToLexicalJSON({ text: 'English text' }),
|
||||
lexicalBlocksSubLocalized: textToLexicalJSON({ text: 'English text' }),
|
||||
},
|
||||
locale: 'en',
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
await _payload.update({
|
||||
collection: lexicalLocalizedFieldsSlug,
|
||||
id: lexicalLocalizedDoc1.id,
|
||||
data: {
|
||||
title: 'Localized Lexical es',
|
||||
lexicalSimple: textToLexicalJSON({ text: 'Spanish text' }),
|
||||
lexicalBlocksLocalized: textToLexicalJSON({ text: 'Spanish text' }),
|
||||
lexicalBlocksSubLocalized: textToLexicalJSON({ text: 'Spanish text' }),
|
||||
},
|
||||
locale: 'es',
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
const lexicalLocalizedDoc2 = await _payload.create({
|
||||
collection: lexicalLocalizedFieldsSlug,
|
||||
data: {
|
||||
title: 'Localized Lexical en 2',
|
||||
lexicalSimple: textToLexicalJSON({
|
||||
text: 'English text 2',
|
||||
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
||||
}),
|
||||
lexicalBlocksLocalized: textToLexicalJSON({
|
||||
text: 'English text 2',
|
||||
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
||||
}),
|
||||
lexicalBlocksSubLocalized: textToLexicalJSON({
|
||||
text: 'English text 2',
|
||||
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
||||
}),
|
||||
},
|
||||
locale: 'en',
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
await _payload.update({
|
||||
collection: lexicalLocalizedFieldsSlug,
|
||||
id: lexicalLocalizedDoc2.id,
|
||||
data: {
|
||||
title: 'Localized Lexical es 2',
|
||||
lexicalSimple: textToLexicalJSON({
|
||||
text: 'Spanish text 2',
|
||||
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
||||
}),
|
||||
lexicalBlocksLocalized: textToLexicalJSON({
|
||||
text: 'Spanish text 2',
|
||||
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
||||
}),
|
||||
},
|
||||
locale: 'es',
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
await _payload.create({
|
||||
collection: lexicalMigrateFieldsSlug,
|
||||
data: lexicalMigrateDocWithRelId,
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
export const usersSlug = 'users' as const
|
||||
export const arrayFieldsSlug = 'array-fields' as const
|
||||
export const blockFieldsSlug = 'block-fields' as const
|
||||
export const checkboxFieldsSlug = 'checkbox-fields' as const
|
||||
export const codeFieldsSlug = 'code-fields' as const
|
||||
export const collapsibleFieldsSlug = 'collapsible-fields' as const
|
||||
export const conditionalLogicSlug = 'conditional-logic' as const
|
||||
export const dateFieldsSlug = 'date-fields' as const
|
||||
export const groupFieldsSlug = 'group-fields' as const
|
||||
export const indexedFieldsSlug = 'indexed-fields' as const
|
||||
export const jsonFieldsSlug = 'json-fields' as const
|
||||
export const lexicalFieldsSlug = 'lexical-fields' as const
|
||||
export const lexicalMigrateFieldsSlug = 'lexical-migrate-fields' as const
|
||||
export const numberFieldsSlug = 'number-fields' as const
|
||||
export const pointFieldsSlug = 'point-fields' as const
|
||||
export const radioFieldsSlug = 'radio-fields' as const
|
||||
export const relationshipFieldsSlug = 'relationship-fields' as const
|
||||
export const richTextFieldsSlug = 'rich-text-fields' as const
|
||||
export const rowFieldsSlug = 'row-fields' as const
|
||||
export const selectFieldsSlug = 'select-fields' as const
|
||||
export const tabsFieldsSlug = 'tabs-fields' as const
|
||||
export const textFieldsSlug = 'text-fields' as const
|
||||
export const uploadsSlug = 'uploads' as const
|
||||
export const uploads2Slug = 'uploads2' as const
|
||||
export const uploads3Slug = 'uploads3' as const
|
||||
export const usersSlug = 'users'
|
||||
export const arrayFieldsSlug = 'array-fields'
|
||||
export const blockFieldsSlug = 'block-fields'
|
||||
export const checkboxFieldsSlug = 'checkbox-fields'
|
||||
export const codeFieldsSlug = 'code-fields'
|
||||
export const collapsibleFieldsSlug = 'collapsible-fields'
|
||||
export const conditionalLogicSlug = 'conditional-logic'
|
||||
export const dateFieldsSlug = 'date-fields'
|
||||
export const groupFieldsSlug = 'group-fields'
|
||||
export const indexedFieldsSlug = 'indexed-fields'
|
||||
export const jsonFieldsSlug = 'json-fields'
|
||||
export const lexicalFieldsSlug = 'lexical-fields'
|
||||
export const lexicalLocalizedFieldsSlug = 'lexical-localized-fields'
|
||||
export const lexicalMigrateFieldsSlug = 'lexical-migrate-fields'
|
||||
export const numberFieldsSlug = 'number-fields'
|
||||
export const pointFieldsSlug = 'point-fields'
|
||||
export const radioFieldsSlug = 'radio-fields'
|
||||
export const relationshipFieldsSlug = 'relationship-fields'
|
||||
export const richTextFieldsSlug = 'rich-text-fields'
|
||||
export const rowFieldsSlug = 'row-fields'
|
||||
export const selectFieldsSlug = 'select-fields'
|
||||
export const tabsFieldsSlug = 'tabs-fields'
|
||||
export const textFieldsSlug = 'text-fields'
|
||||
export const uploadsSlug = 'uploads'
|
||||
export const uploads2Slug = 'uploads2'
|
||||
export const uploads3Slug = 'uploads3'
|
||||
export const collectionSlugs = [
|
||||
usersSlug,
|
||||
arrayFieldsSlug,
|
||||
|
||||
Reference in New Issue
Block a user