chore: separate Lexical tests into dedicated suite (#12047)
Lexical tests comprise almost half of the collections in the fields suite, and are starting to become complex to manage. They are sometimes related to other auxiliary collections, so refactoring one test sometimes breaks another, seemingly unrelated one. In addition, the fields suite is very large, taking a long time to compile. This will make it faster. Some ideas for future refactorings: - 3 main collections: defaultFeatures, fully featured, and legacy. Legacy is the current one that has multiple editors and could later be migrated to the first two. - Avoid collections with more than 1 editor. - Create reseed buttons to restore the editor to certain states, to avoid a proliferation of collections and documents. - Reduce the complexity of the three auxiliary collections (text, array, upload), which are rarely or never used and have many fields designed for tests in the fields suite.
This commit is contained in:
@@ -1,122 +0,0 @@
|
||||
'use client'
|
||||
import type { DefaultNodeTypes, SerializedBlockNode } from '@payloadcms/richtext-lexical'
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import { getRestPopulateFn } from '@payloadcms/richtext-lexical/client'
|
||||
import {
|
||||
convertLexicalToHTML,
|
||||
type HTMLConvertersFunction,
|
||||
} from '@payloadcms/richtext-lexical/html'
|
||||
import {
|
||||
convertLexicalToHTMLAsync,
|
||||
type HTMLConvertersFunctionAsync,
|
||||
} from '@payloadcms/richtext-lexical/html-async'
|
||||
import { type JSXConvertersFunction, RichText } from '@payloadcms/richtext-lexical/react'
|
||||
import { useConfig, useDocumentInfo, usePayloadAPI } from '@payloadcms/ui'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
const jsxConverters: JSXConvertersFunction<DefaultNodeTypes | SerializedBlockNode<any>> = ({
|
||||
defaultConverters,
|
||||
}) => ({
|
||||
...defaultConverters,
|
||||
blocks: {
|
||||
myTextBlock: ({ node }) => <div style={{ backgroundColor: 'red' }}>{node.fields.text}</div>,
|
||||
relationshipBlock: ({ node, nodesToJSX }) => {
|
||||
return <p>Test</p>
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const htmlConverters: HTMLConvertersFunction<DefaultNodeTypes | SerializedBlockNode<any>> = ({
|
||||
defaultConverters,
|
||||
}) => ({
|
||||
...defaultConverters,
|
||||
blocks: {
|
||||
myTextBlock: ({ node }) => `<div style="background-color: red;">${node.fields.text}</div>`,
|
||||
relationshipBlock: () => {
|
||||
return `<p>Test</p>`
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const htmlConvertersAsync: HTMLConvertersFunctionAsync<
|
||||
DefaultNodeTypes | SerializedBlockNode<any>
|
||||
> = ({ defaultConverters }) => ({
|
||||
...defaultConverters,
|
||||
blocks: {
|
||||
myTextBlock: ({ node }) => `<div style="background-color: red;">${node.fields.text}</div>`,
|
||||
relationshipBlock: () => {
|
||||
return `<p>Test</p>`
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const LexicalRendered: React.FC = () => {
|
||||
const { id, collectionSlug } = useDocumentInfo()
|
||||
|
||||
const {
|
||||
config: {
|
||||
routes: { api },
|
||||
serverURL,
|
||||
},
|
||||
} = useConfig()
|
||||
|
||||
const [{ data }] = usePayloadAPI(`${serverURL}${api}/${collectionSlug}/${id}`, {
|
||||
initialParams: {
|
||||
depth: 1,
|
||||
},
|
||||
})
|
||||
|
||||
const [{ data: unpopulatedData }] = usePayloadAPI(`${serverURL}${api}/${collectionSlug}/${id}`, {
|
||||
initialParams: {
|
||||
depth: 0,
|
||||
},
|
||||
})
|
||||
|
||||
const html: null | string = useMemo(() => {
|
||||
if (!data.lexicalWithBlocks) {
|
||||
return null
|
||||
}
|
||||
|
||||
return convertLexicalToHTML({
|
||||
converters: htmlConverters,
|
||||
data: data.lexicalWithBlocks as SerializedEditorState,
|
||||
})
|
||||
}, [data.lexicalWithBlocks])
|
||||
|
||||
const [htmlFromUnpopulatedData, setHtmlFromUnpopulatedData] = useState<null | string>(null)
|
||||
|
||||
useEffect(() => {
|
||||
async function convert() {
|
||||
const html = await convertLexicalToHTMLAsync({
|
||||
converters: htmlConvertersAsync,
|
||||
data: unpopulatedData.lexicalWithBlocks as SerializedEditorState,
|
||||
populate: getRestPopulateFn({
|
||||
apiURL: `${serverURL}${api}`,
|
||||
}),
|
||||
})
|
||||
|
||||
setHtmlFromUnpopulatedData(html)
|
||||
}
|
||||
void convert()
|
||||
}, [unpopulatedData.lexicalWithBlocks, api, serverURL])
|
||||
|
||||
if (!data.lexicalWithBlocks) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Rendered JSX:</h1>
|
||||
<RichText converters={jsxConverters} data={data.lexicalWithBlocks as SerializedEditorState} />
|
||||
<h1>Rendered HTML:</h1>
|
||||
{html && <div dangerouslySetInnerHTML={{ __html: html }} />}
|
||||
<h1>Rendered HTML 2:</h1>
|
||||
{htmlFromUnpopulatedData && (
|
||||
<div dangerouslySetInnerHTML={{ __html: htmlFromUnpopulatedData }} />
|
||||
)}
|
||||
<h1>Raw JSON:</h1>
|
||||
<pre>{JSON.stringify(data.lexicalWithBlocks, null, 2)}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { $isInlineBlockNode, createClientFeature } from '@payloadcms/richtext-lexical/client'
|
||||
import { $getSelection } from '@payloadcms/richtext-lexical/lexical'
|
||||
import { CloseMenuIcon } from '@payloadcms/ui'
|
||||
|
||||
import { ModifyInlineBlockPlugin } from './plugin.js'
|
||||
|
||||
export const ModifyInlineBlockFeatureClient = createClientFeature({
|
||||
plugins: [
|
||||
{
|
||||
Component: ModifyInlineBlockPlugin,
|
||||
position: 'normal',
|
||||
},
|
||||
],
|
||||
toolbarFixed: {
|
||||
groups: [
|
||||
{
|
||||
key: 'debug',
|
||||
items: [
|
||||
{
|
||||
ChildComponent: CloseMenuIcon,
|
||||
key: 'setKeyToDebug',
|
||||
label: 'Set Key To Debug',
|
||||
onSelect({ editor }) {
|
||||
editor.update(() => {
|
||||
const selection = $getSelection()
|
||||
|
||||
// Check if selection consist of 1 node and that its an inlineblocknode
|
||||
const nodes = selection.getNodes()
|
||||
|
||||
if (nodes.length !== 1) {
|
||||
return
|
||||
}
|
||||
|
||||
const node = nodes[0]
|
||||
|
||||
if (!$isInlineBlockNode(node)) {
|
||||
return
|
||||
}
|
||||
|
||||
const fields = node.getFields()
|
||||
|
||||
node.setFields({
|
||||
blockType: fields.blockType,
|
||||
id: fields.id,
|
||||
key: 'value2',
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
type: 'buttons',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createServerFeature } from '@payloadcms/richtext-lexical'
|
||||
|
||||
export const ModifyInlineBlockFeature = createServerFeature({
|
||||
key: 'ModifyInlineBlockFeature',
|
||||
feature: {
|
||||
ClientFeature:
|
||||
'./collections/Lexical/ModifyInlineBlockFeature/feature.client.js#ModifyInlineBlockFeatureClient',
|
||||
},
|
||||
})
|
||||
@@ -1,7 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type { PluginComponent } from '@payloadcms/richtext-lexical'
|
||||
|
||||
export const ModifyInlineBlockPlugin: PluginComponent = () => {
|
||||
return null
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
'use client'
|
||||
import {
|
||||
BlockCollapsible,
|
||||
BlockEditButton,
|
||||
BlockRemoveButton,
|
||||
} from '@payloadcms/richtext-lexical/client'
|
||||
import { useFormFields } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
export const BlockComponent: React.FC = () => {
|
||||
const key = useFormFields(([fields]) => fields.key)
|
||||
|
||||
return (
|
||||
<BlockCollapsible>
|
||||
MY BLOCK COMPONENT. Value: {(key?.value as string) ?? '<no value>'}
|
||||
Edit: <BlockEditButton />
|
||||
<BlockRemoveButton />
|
||||
</BlockCollapsible>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { BlocksFieldServerComponent } from 'payload'
|
||||
|
||||
import { BlockCollapsible } from '@payloadcms/richtext-lexical/client'
|
||||
import React from 'react'
|
||||
|
||||
export const BlockComponentRSC: BlocksFieldServerComponent = (props) => {
|
||||
const { siblingData } = props
|
||||
|
||||
return <BlockCollapsible>Data: {siblingData?.key ?? ''}</BlockCollapsible>
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useFormFields } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
export const LabelComponent: React.FC = () => {
|
||||
const key = useFormFields(([fields]) => fields.key)
|
||||
|
||||
return <div>{(key?.value as string) ?? '<no value>'}yaya</div>
|
||||
}
|
||||
@@ -1,446 +0,0 @@
|
||||
import type { ArrayField, Block, TextFieldSingleValidation } from 'payload'
|
||||
|
||||
import { BlocksFeature, FixedToolbarFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { textFieldsSlug } from '../Text/shared.js'
|
||||
|
||||
async function asyncFunction(param: string) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(param?.toUpperCase())
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
export const FilterOptionsBlock: Block = {
|
||||
slug: 'filterOptionsBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'groupText',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'dependsOnDocData',
|
||||
type: 'relationship',
|
||||
relationTo: 'text-fields',
|
||||
filterOptions: ({ data }) => {
|
||||
if (!data.title) {
|
||||
return true
|
||||
}
|
||||
return {
|
||||
text: {
|
||||
equals: data.title,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dependsOnSiblingData',
|
||||
type: 'relationship',
|
||||
relationTo: 'text-fields',
|
||||
filterOptions: ({ siblingData }) => {
|
||||
// @ts-expect-error
|
||||
if (!siblingData?.groupText) {
|
||||
return true
|
||||
}
|
||||
return {
|
||||
text: {
|
||||
equals: (siblingData as any)?.groupText,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dependsOnBlockData',
|
||||
type: 'relationship',
|
||||
relationTo: 'text-fields',
|
||||
filterOptions: ({ blockData }) => {
|
||||
if (!blockData?.text) {
|
||||
return true
|
||||
}
|
||||
return {
|
||||
text: {
|
||||
equals: blockData?.text,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const ValidationBlock: Block = {
|
||||
slug: 'validationBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'groupText',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'textDependsOnDocData',
|
||||
type: 'text',
|
||||
validate: ((value, { data }) => {
|
||||
if ((data as any)?.title === 'invalid') {
|
||||
return 'doc title cannot be invalid'
|
||||
}
|
||||
return true
|
||||
}) as TextFieldSingleValidation,
|
||||
},
|
||||
{
|
||||
name: 'textDependsOnSiblingData',
|
||||
type: 'text',
|
||||
validate: ((value, { siblingData }) => {
|
||||
if ((siblingData as any)?.groupText === 'invalid') {
|
||||
return 'textDependsOnSiblingData sibling field cannot be invalid'
|
||||
}
|
||||
}) as TextFieldSingleValidation,
|
||||
},
|
||||
{
|
||||
name: 'textDependsOnBlockData',
|
||||
type: 'text',
|
||||
validate: ((value, { blockData }) => {
|
||||
if ((blockData as any)?.text === 'invalid') {
|
||||
return 'textDependsOnBlockData sibling field cannot be invalid'
|
||||
}
|
||||
}) as TextFieldSingleValidation,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const AsyncHooksBlock: Block = {
|
||||
slug: 'asyncHooksBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'test1',
|
||||
label: 'Text',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
afterRead: [
|
||||
({ value }) => {
|
||||
return value?.toUpperCase()
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'test2',
|
||||
label: 'Text',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
afterRead: [
|
||||
async ({ value }) => {
|
||||
const valuenew = await asyncFunction(value)
|
||||
return valuenew
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const BlockColumns = ({ name }: { name: string }): ArrayField => ({
|
||||
type: 'array',
|
||||
name,
|
||||
interfaceName: 'BlockColumns',
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'subArray',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'requiredText',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
export const ConditionalLayoutBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
label: 'Layout',
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
options: ['1', '2', '3'],
|
||||
defaultValue: '1',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
...BlockColumns({ name: 'columns' }),
|
||||
admin: {
|
||||
condition: (data, siblingData) => {
|
||||
return ['1'].includes(siblingData.layout)
|
||||
},
|
||||
},
|
||||
minRows: 1,
|
||||
maxRows: 1,
|
||||
},
|
||||
{
|
||||
...BlockColumns({ name: 'columns2' }),
|
||||
admin: {
|
||||
condition: (data, siblingData) => {
|
||||
return ['2'].includes(siblingData.layout)
|
||||
},
|
||||
},
|
||||
minRows: 2,
|
||||
maxRows: 2,
|
||||
},
|
||||
{
|
||||
...BlockColumns({ name: 'columns3' }),
|
||||
admin: {
|
||||
condition: (data, siblingData) => {
|
||||
return ['3'].includes(siblingData.layout)
|
||||
},
|
||||
},
|
||||
minRows: 3,
|
||||
maxRows: 3,
|
||||
},
|
||||
],
|
||||
slug: 'conditionalLayout',
|
||||
}
|
||||
|
||||
export const TextBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
slug: 'textRequired',
|
||||
}
|
||||
|
||||
export const RadioButtonsBlock: Block = {
|
||||
interfaceName: 'LexicalBlocksRadioButtonsBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'radioButtons',
|
||||
type: 'radio',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: 'option2',
|
||||
},
|
||||
{
|
||||
label: 'Option 3',
|
||||
value: 'option3',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
slug: 'radioButtons',
|
||||
}
|
||||
|
||||
export const RichTextBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
name: 'richTextField',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
FixedToolbarFeature(),
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'subRichTextField',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({}),
|
||||
},
|
||||
{
|
||||
name: 'subUploadField',
|
||||
type: 'upload',
|
||||
relationTo: 'uploads',
|
||||
},
|
||||
],
|
||||
slug: 'lexicalAndUploadBlock',
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
slug: 'richTextBlock',
|
||||
}
|
||||
|
||||
export const UploadAndRichTextBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
name: 'upload',
|
||||
type: 'upload',
|
||||
relationTo: 'uploads',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor(),
|
||||
},
|
||||
],
|
||||
slug: 'uploadAndRichText',
|
||||
}
|
||||
|
||||
export const RelationshipHasManyBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
name: 'rel',
|
||||
type: 'relationship',
|
||||
hasMany: true,
|
||||
relationTo: [textFieldsSlug, 'uploads'],
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
slug: 'relationshipHasManyBlock',
|
||||
}
|
||||
export const RelationshipBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
name: 'rel',
|
||||
type: 'relationship',
|
||||
relationTo: 'uploads',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
slug: 'relationshipBlock',
|
||||
}
|
||||
|
||||
export const SelectFieldBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
name: 'select',
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: 'option2',
|
||||
},
|
||||
{
|
||||
label: 'Option 3',
|
||||
value: 'option3',
|
||||
},
|
||||
{
|
||||
label: 'Option 4',
|
||||
value: 'option4',
|
||||
},
|
||||
{
|
||||
label: 'Option 5',
|
||||
value: 'option5',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
slug: 'select',
|
||||
}
|
||||
|
||||
export const SubBlockBlock: Block = {
|
||||
slug: 'subBlockLexical',
|
||||
fields: [
|
||||
{
|
||||
name: 'subBlocksLexical',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'contentBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
editor: lexicalEditor(),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'textArea',
|
||||
fields: [
|
||||
{
|
||||
name: 'content',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
SelectFieldBlock,
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const TabBlock: Block = {
|
||||
slug: 'tabBlock',
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
label: 'Tab1',
|
||||
name: 'tab1',
|
||||
fields: [
|
||||
{
|
||||
name: 'text1',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Tab2',
|
||||
name: 'tab2',
|
||||
fields: [
|
||||
{
|
||||
name: 'text2',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const CodeBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
name: 'code',
|
||||
type: 'code',
|
||||
},
|
||||
],
|
||||
slug: 'code',
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type { SerializedParagraphNode, SerializedTextNode } from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { useForm } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
export const ClearState = ({ fieldName }: { fieldName: string }) => {
|
||||
const { dispatchFields, fields } = useForm()
|
||||
|
||||
const clearState = React.useCallback(() => {
|
||||
const newState = {
|
||||
root: {
|
||||
type: 'root',
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '',
|
||||
version: 1,
|
||||
} as SerializedTextNode,
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
textFormat: 0,
|
||||
textStyle: '',
|
||||
version: 1,
|
||||
} as SerializedParagraphNode,
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
},
|
||||
}
|
||||
dispatchFields({
|
||||
type: 'REPLACE_STATE',
|
||||
state: {
|
||||
...fields,
|
||||
[fieldName]: {
|
||||
...fields[fieldName],
|
||||
initialValue: newState,
|
||||
value: newState,
|
||||
},
|
||||
},
|
||||
})
|
||||
}, [dispatchFields, fields, fieldName])
|
||||
|
||||
return (
|
||||
<button id={`clear-lexical-${fieldName}`} onClick={clearState} type="button">
|
||||
Clear State
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { generateLexicalRichText } from './generateLexicalRichText.js'
|
||||
|
||||
export const lexicalDocData = {
|
||||
title: 'Rich Text',
|
||||
lexicalWithBlocks: generateLexicalRichText(),
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,321 +0,0 @@
|
||||
import type {
|
||||
SerializedBlockNode,
|
||||
SerializedParagraphNode,
|
||||
SerializedTextNode,
|
||||
SerializedUploadNode,
|
||||
TypedEditorState,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
export function generateLexicalRichText(): TypedEditorState<
|
||||
SerializedBlockNode | SerializedParagraphNode | SerializedTextNode | SerializedUploadNode
|
||||
> {
|
||||
return {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'Upload Node:',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
textFormat: 0,
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'upload',
|
||||
version: 2,
|
||||
id: '665d105a91e1c337ba8308dd',
|
||||
fields: {
|
||||
caption: {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'Relationship inside Upload Caption:',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 2,
|
||||
relationTo: 'text-fields',
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
},
|
||||
},
|
||||
},
|
||||
relationTo: 'uploads',
|
||||
value: '{{UPLOAD_DOC_ID}}',
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'block',
|
||||
version: 2,
|
||||
fields: {
|
||||
id: '65298b13db4ef8c744a7faaa',
|
||||
rel: '{{UPLOAD_DOC_ID}}',
|
||||
blockName: 'Block Node, with Relationship Field',
|
||||
blockType: 'relationshipBlock',
|
||||
},
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'block',
|
||||
version: 2,
|
||||
fields: {
|
||||
id: '6565c8668294bf824c24d4a4',
|
||||
blockName: '',
|
||||
blockType: 'relationshipHasManyBlock',
|
||||
rel: [
|
||||
{
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
relationTo: 'text-fields',
|
||||
},
|
||||
{
|
||||
value: '{{UPLOAD_DOC_ID}}',
|
||||
relationTo: 'uploads',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'block',
|
||||
version: 2,
|
||||
fields: {
|
||||
id: '65298b1ddb4ef8c744a7faab',
|
||||
richTextField: {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 2,
|
||||
relationTo: 'rich-text-fields',
|
||||
value: '{{RICH_TEXT_DOC_ID}}',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'Some text below relationship node 1',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
},
|
||||
},
|
||||
blockName: 'Block Node, with RichText Field, with Relationship Node',
|
||||
blockType: 'richTextBlock',
|
||||
},
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'block',
|
||||
version: 2,
|
||||
fields: {
|
||||
id: '65298b2bdb4ef8c744a7faac',
|
||||
blockName: 'Block Node, with Blocks Field, With RichText Field, With Relationship Node',
|
||||
blockType: 'subBlockLexical',
|
||||
subBlocksLexical: [
|
||||
{
|
||||
id: '65298b2edb4ef8c744a7faad',
|
||||
richText: {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 2,
|
||||
relationTo: 'text-fields',
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'Some text below relationship node 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
},
|
||||
},
|
||||
blockType: 'contentBlock',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'block',
|
||||
version: 2,
|
||||
fields: {
|
||||
id: '65298b49db4ef8c744a7faae',
|
||||
upload: '{{UPLOAD_DOC_ID}}',
|
||||
blockName: 'Block Node, With Upload Field',
|
||||
blockType: 'uploadAndRichText',
|
||||
},
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
textFormat: 0,
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'block',
|
||||
version: 2,
|
||||
fields: {
|
||||
id: '65532e49fe515eb112e605a3',
|
||||
blockName: 'Radio Buttons 1',
|
||||
blockType: 'radioButtons',
|
||||
radioButtons: 'option1',
|
||||
},
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'block',
|
||||
version: 2,
|
||||
fields: {
|
||||
id: '65532e50fe515eb112e605a4',
|
||||
blockName: 'Radio Buttons 2',
|
||||
blockType: 'radioButtons',
|
||||
radioButtons: 'option1',
|
||||
},
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
textFormat: 0,
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'block',
|
||||
version: 2,
|
||||
fields: {
|
||||
id: '65588bfa80fb5a147a378e74',
|
||||
blockName: '',
|
||||
blockType: 'conditionalLayout',
|
||||
layout: '1',
|
||||
columns: [
|
||||
{
|
||||
id: '65588bfb80fb5a147a378e75',
|
||||
text: 'text in conditionalLayout block',
|
||||
},
|
||||
],
|
||||
},
|
||||
}, // Do not remove this blocks node. It ensures that validation passes when it's created
|
||||
{
|
||||
children: [],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
textFormat: 0,
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'block',
|
||||
version: 2,
|
||||
fields: {
|
||||
id: '666c9dfd189d72626ea301f9',
|
||||
blockName: '',
|
||||
tab1: {
|
||||
text1: 'Some text1',
|
||||
},
|
||||
tab2: {
|
||||
text2: 'Some text2',
|
||||
},
|
||||
blockType: 'tabBlock',
|
||||
},
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'block',
|
||||
version: 2,
|
||||
fields: {
|
||||
id: '666c9e0b189d72626ea301fa',
|
||||
blockName: '',
|
||||
blockType: 'code',
|
||||
code: 'Some code\nhello\nworld',
|
||||
},
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,408 +0,0 @@
|
||||
import type { ServerEditorConfig } from '@payloadcms/richtext-lexical'
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
import type { Block, BlockSlug, CollectionConfig } from 'payload'
|
||||
|
||||
import {
|
||||
BlocksFeature,
|
||||
defaultEditorFeatures,
|
||||
EXPERIMENTAL_TableFeature,
|
||||
FixedToolbarFeature,
|
||||
getEnabledNodes,
|
||||
HeadingFeature,
|
||||
lexicalEditor,
|
||||
LinkFeature,
|
||||
sanitizeServerEditorConfig,
|
||||
TreeViewFeature,
|
||||
UploadFeature,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
import { createHeadlessEditor } from '@payloadcms/richtext-lexical/lexical/headless'
|
||||
import { $convertToMarkdownString } from '@payloadcms/richtext-lexical/lexical/markdown'
|
||||
|
||||
import { lexicalFieldsSlug } from '../../slugs.js'
|
||||
import {
|
||||
AsyncHooksBlock,
|
||||
CodeBlock,
|
||||
ConditionalLayoutBlock,
|
||||
FilterOptionsBlock,
|
||||
RadioButtonsBlock,
|
||||
RelationshipBlock,
|
||||
RelationshipHasManyBlock,
|
||||
RichTextBlock,
|
||||
SelectFieldBlock,
|
||||
SubBlockBlock,
|
||||
TabBlock,
|
||||
TextBlock,
|
||||
UploadAndRichTextBlock,
|
||||
ValidationBlock,
|
||||
} from './blocks.js'
|
||||
import { ModifyInlineBlockFeature } from './ModifyInlineBlockFeature/feature.server.js'
|
||||
|
||||
export const lexicalBlocks: (Block | BlockSlug)[] = [
|
||||
ValidationBlock,
|
||||
FilterOptionsBlock,
|
||||
AsyncHooksBlock,
|
||||
RichTextBlock,
|
||||
TextBlock,
|
||||
UploadAndRichTextBlock,
|
||||
SelectFieldBlock,
|
||||
RelationshipBlock,
|
||||
RelationshipHasManyBlock,
|
||||
SubBlockBlock,
|
||||
RadioButtonsBlock,
|
||||
ConditionalLayoutBlock,
|
||||
TabBlock,
|
||||
CodeBlock,
|
||||
{
|
||||
slug: 'myBlock',
|
||||
admin: {
|
||||
components: {},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'key',
|
||||
label: () => {
|
||||
return 'Key'
|
||||
},
|
||||
type: 'select',
|
||||
options: ['value1', 'value2', 'value3'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'myBlockWithLabel',
|
||||
admin: {
|
||||
components: {
|
||||
Label: '/collections/Lexical/blockComponents/LabelComponent.js#LabelComponent',
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'key',
|
||||
label: () => {
|
||||
return 'Key'
|
||||
},
|
||||
type: 'select',
|
||||
options: ['value1', 'value2', 'value3'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'myBlockWithBlock',
|
||||
admin: {
|
||||
components: {
|
||||
Block: '/collections/Lexical/blockComponents/BlockComponent.js#BlockComponent',
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'key',
|
||||
label: () => {
|
||||
return 'Key'
|
||||
},
|
||||
type: 'select',
|
||||
options: ['value1', 'value2', 'value3'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'BlockRSC',
|
||||
|
||||
admin: {
|
||||
components: {
|
||||
Block: '/collections/Lexical/blockComponents/BlockComponentRSC.js#BlockComponentRSC',
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'key',
|
||||
label: () => {
|
||||
return 'Key'
|
||||
},
|
||||
type: 'select',
|
||||
options: ['value1', 'value2', 'value3'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'myBlockWithBlockAndLabel',
|
||||
admin: {
|
||||
components: {
|
||||
Block: '/collections/Lexical/blockComponents/BlockComponent.js#BlockComponent',
|
||||
Label: '/collections/Lexical/blockComponents/LabelComponent.js#LabelComponent',
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'key',
|
||||
label: () => {
|
||||
return 'Key'
|
||||
},
|
||||
type: 'select',
|
||||
options: ['value1', 'value2', 'value3'],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const lexicalInlineBlocks: (Block | BlockSlug)[] = [
|
||||
{
|
||||
slug: 'AvatarGroup',
|
||||
interfaceName: 'AvatarGroupBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'avatars',
|
||||
type: 'array',
|
||||
minRows: 1,
|
||||
maxRows: 6,
|
||||
fields: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'uploads',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'myInlineBlock',
|
||||
admin: {
|
||||
components: {},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'key',
|
||||
label: () => {
|
||||
return 'Key'
|
||||
},
|
||||
type: 'select',
|
||||
options: ['value1', 'value2', 'value3'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'myInlineBlockWithLabel',
|
||||
admin: {
|
||||
components: {
|
||||
Label: '/collections/Lexical/inlineBlockComponents/LabelComponent.js#LabelComponent',
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'key',
|
||||
label: () => {
|
||||
return 'Key'
|
||||
},
|
||||
type: 'select',
|
||||
options: ['value1', 'value2', 'value3'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'myInlineBlockWithBlock',
|
||||
admin: {
|
||||
components: {
|
||||
Block: '/collections/Lexical/inlineBlockComponents/BlockComponent.js#BlockComponent',
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'key',
|
||||
label: () => {
|
||||
return 'Key'
|
||||
},
|
||||
type: 'select',
|
||||
options: ['value1', 'value2', 'value3'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'myInlineBlockWithBlockAndLabel',
|
||||
admin: {
|
||||
components: {
|
||||
Block: '/collections/Lexical/inlineBlockComponents/BlockComponent.js#BlockComponent',
|
||||
Label: '/collections/Lexical/inlineBlockComponents/LabelComponent.js#LabelComponent',
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'key',
|
||||
label: () => {
|
||||
return 'Key'
|
||||
},
|
||||
type: 'select',
|
||||
options: ['value1', 'value2', 'value3'],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const getLexicalFieldsCollection: (args: {
|
||||
blocks: (Block | BlockSlug)[]
|
||||
inlineBlocks: (Block | BlockSlug)[]
|
||||
}) => CollectionConfig = ({ blocks, inlineBlocks }) => {
|
||||
const editorConfig: ServerEditorConfig = {
|
||||
features: [
|
||||
...defaultEditorFeatures,
|
||||
//TestRecorderFeature(),
|
||||
TreeViewFeature(),
|
||||
//HTMLConverterFeature(),
|
||||
FixedToolbarFeature(),
|
||||
LinkFeature({
|
||||
fields: ({ defaultFields }) => [
|
||||
...defaultFields,
|
||||
{
|
||||
name: 'rel',
|
||||
type: 'select',
|
||||
admin: {
|
||||
description:
|
||||
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
|
||||
},
|
||||
hasMany: true,
|
||||
label: 'Rel Attribute',
|
||||
options: ['noopener', 'noreferrer', 'nofollow'],
|
||||
},
|
||||
],
|
||||
}),
|
||||
UploadFeature({
|
||||
collections: {
|
||||
uploads: {
|
||||
fields: [
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor(),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
ModifyInlineBlockFeature(),
|
||||
BlocksFeature({
|
||||
blocks,
|
||||
inlineBlocks,
|
||||
}),
|
||||
EXPERIMENTAL_TableFeature(),
|
||||
],
|
||||
}
|
||||
return {
|
||||
slug: lexicalFieldsSlug,
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
admin: {
|
||||
listSearchableFields: ['title', 'richTextLexicalCustomFields'],
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'lexicalRootEditor',
|
||||
type: 'richText',
|
||||
},
|
||||
{
|
||||
name: 'lexicalSimple',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
//TestRecorderFeature(),
|
||||
TreeViewFeature(),
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
RichTextBlock,
|
||||
TextBlock,
|
||||
UploadAndRichTextBlock,
|
||||
SelectFieldBlock,
|
||||
RelationshipBlock,
|
||||
RelationshipHasManyBlock,
|
||||
SubBlockBlock,
|
||||
RadioButtonsBlock,
|
||||
ConditionalLayoutBlock,
|
||||
],
|
||||
}),
|
||||
HeadingFeature({ enabledHeadingSizes: ['h2', 'h4'] }),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'ui',
|
||||
name: 'clearLexicalState',
|
||||
admin: {
|
||||
components: {
|
||||
Field: {
|
||||
path: '/collections/Lexical/components/ClearState.js#ClearState',
|
||||
clientProps: {
|
||||
fieldName: 'lexicalSimple',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lexicalWithBlocks',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
admin: {
|
||||
hideGutter: false,
|
||||
},
|
||||
features: editorConfig.features,
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
//{
|
||||
// name: 'rendered',
|
||||
// type: 'ui',
|
||||
// admin: {
|
||||
// components: {
|
||||
// Field: './collections/Lexical/LexicalRendered.js#LexicalRendered',
|
||||
// },
|
||||
// },
|
||||
//},
|
||||
{
|
||||
name: 'lexicalWithBlocks_markdown',
|
||||
type: 'textarea',
|
||||
hooks: {
|
||||
afterRead: [
|
||||
async ({ data, req, siblingData }) => {
|
||||
const yourSanitizedEditorConfig = await sanitizeServerEditorConfig(
|
||||
editorConfig,
|
||||
req.payload.config,
|
||||
)
|
||||
|
||||
const headlessEditor = createHeadlessEditor({
|
||||
nodes: getEnabledNodes({
|
||||
editorConfig: yourSanitizedEditorConfig,
|
||||
}),
|
||||
})
|
||||
|
||||
const yourEditorState: SerializedEditorState = siblingData.lexicalWithBlocks
|
||||
try {
|
||||
headlessEditor.update(
|
||||
() => {
|
||||
headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState))
|
||||
},
|
||||
{ discrete: true },
|
||||
)
|
||||
} catch (e) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
// Export to markdown
|
||||
let markdown: string = ''
|
||||
headlessEditor.getEditorState().read(() => {
|
||||
markdown = $convertToMarkdownString(
|
||||
yourSanitizedEditorConfig?.features?.markdownTransformers,
|
||||
)
|
||||
})
|
||||
return markdown
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import {
|
||||
InlineBlockContainer,
|
||||
InlineBlockEditButton,
|
||||
InlineBlockLabel,
|
||||
InlineBlockRemoveButton,
|
||||
} from '@payloadcms/richtext-lexical/client'
|
||||
import React from 'react'
|
||||
|
||||
export const BlockComponent: React.FC = () => {
|
||||
return (
|
||||
<InlineBlockContainer>
|
||||
<p>Test</p>
|
||||
<InlineBlockEditButton />
|
||||
<InlineBlockLabel />
|
||||
<InlineBlockRemoveButton />
|
||||
</InlineBlockContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useFormFields } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
export const LabelComponent: React.FC = () => {
|
||||
const key = useFormFields(([fields]) => fields.key)
|
||||
|
||||
return <div>{(key?.value as string) ?? '<no value>'}yaya</div>
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export const loremIpsum =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam hendrerit nisi sed sollicitudin pellentesque. Nunc posuere purus rhoncus pulvinar aliquam. Ut aliquet tristique nisl vitae volutpat. Nulla aliquet porttitor venenatis. Donec a dui et dui fringilla consectetur id nec massa. Aliquam erat volutpat. Sed ut dui ut lacus dictum fermentum vel tincidunt neque. Sed sed lacinia lectus. Duis sit amet sodales felis. Duis nunc eros, mattis at dui ac, convallis semper risus. In adipiscing ultrices tellus, in suscipit massa vehicula eu.'
|
||||
@@ -1,51 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { defaultEditorFeatures, lexicalEditor, LinkFeature } from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { lexicalAccessControlSlug } from '../../slugs.js'
|
||||
|
||||
export const LexicalAccessControl: CollectionConfig = {
|
||||
slug: lexicalAccessControlSlug,
|
||||
access: {
|
||||
read: () => true,
|
||||
create: () => false,
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: [
|
||||
...defaultEditorFeatures,
|
||||
LinkFeature({
|
||||
fields: ({ defaultFields }) => [
|
||||
...defaultFields,
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'block',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { BlocksFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
|
||||
export const LexicalInBlock: CollectionConfig = {
|
||||
slug: 'LexicalInBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: [
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
{
|
||||
slug: 'blockInLexical',
|
||||
fields: [
|
||||
{
|
||||
name: 'lexicalInBlock',
|
||||
label: 'My Label',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
editor: lexicalEditor(),
|
||||
admin: {
|
||||
description: 'Some Description',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'lexicalInBlock2',
|
||||
fields: [
|
||||
{
|
||||
name: 'lexical',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: [
|
||||
BlocksFeature({
|
||||
inlineBlocks: [
|
||||
{
|
||||
slug: 'inlineBlockInLexical',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
export function generateLexicalLocalizedRichText(text1: string, text2: string, blockID?: string) {
|
||||
return {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: text1,
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
textFormat: 0,
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'block',
|
||||
version: 2,
|
||||
fields: {
|
||||
id: blockID ?? '66685716795f191f08367b1a',
|
||||
blockName: '',
|
||||
textLocalized: text2,
|
||||
counter: 1,
|
||||
blockType: 'blockLexicalLocalized',
|
||||
},
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
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: 'lexicalBlocksSubLocalized',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
description: 'Non-localized field with localized block subfields',
|
||||
},
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
{
|
||||
slug: 'blockLexicalLocalized',
|
||||
fields: [
|
||||
{
|
||||
name: 'textLocalized',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'counter',
|
||||
type: 'number',
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ value }) => {
|
||||
return value ? value + 1 : 1
|
||||
},
|
||||
],
|
||||
afterRead: [
|
||||
({ value }) => {
|
||||
return value ? value * 10 : 10
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'rel',
|
||||
type: 'relationship',
|
||||
relationTo: lexicalLocalizedFieldsSlug,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'lexicalBlocksLocalized',
|
||||
admin: {
|
||||
description: 'Localized field with localized block subfields',
|
||||
},
|
||||
type: 'richText',
|
||||
localized: true,
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
{
|
||||
slug: 'blockLexicalLocalized2',
|
||||
fields: [
|
||||
{
|
||||
name: 'textLocalized',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'rel',
|
||||
type: 'relationship',
|
||||
relationTo: lexicalLocalizedFieldsSlug,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import type { SerializedRelationshipNode } from '@payloadcms/richtext-lexical'
|
||||
import type {
|
||||
SerializedEditorState,
|
||||
SerializedParagraphNode,
|
||||
SerializedTextNode,
|
||||
} from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import { lexicalLocalizedFieldsSlug } from '../../slugs.js'
|
||||
|
||||
export function textToLexicalJSON({
|
||||
text,
|
||||
lexicalLocalizedRelID,
|
||||
}: {
|
||||
lexicalLocalizedRelID?: number | string
|
||||
text: string
|
||||
}): any {
|
||||
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,
|
||||
textFormat: 0,
|
||||
type: 'paragraph',
|
||||
textStyle: '',
|
||||
version: 1,
|
||||
} as SerializedParagraphNode,
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
if (lexicalLocalizedRelID) {
|
||||
editorJSON.root.children.push({
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 2,
|
||||
relationTo: lexicalLocalizedFieldsSlug,
|
||||
value: lexicalLocalizedRelID,
|
||||
} as SerializedRelationshipNode)
|
||||
}
|
||||
|
||||
return editorJSON
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { generateSlateRichText } from '../RichText/generateSlateRichText.js'
|
||||
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData.js'
|
||||
|
||||
export const lexicalMigrateDocData = {
|
||||
title: 'Rich Text',
|
||||
lexicalWithLexicalPluginData: payloadPluginLexicalData,
|
||||
lexicalWithSlateData: [
|
||||
...generateSlateRichText(),
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Some block quote',
|
||||
},
|
||||
],
|
||||
type: 'blockquote',
|
||||
},
|
||||
],
|
||||
arrayWithLexicalField: [
|
||||
{
|
||||
lexicalInArrayField: getSimpleLexicalData('array 1'),
|
||||
},
|
||||
{
|
||||
lexicalInArrayField: getSimpleLexicalData('array 2'),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function getSimpleLexicalData(textContent: string) {
|
||||
return {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: textContent,
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,958 +0,0 @@
|
||||
export const payloadPluginLexicalData = {
|
||||
words: 49,
|
||||
preview:
|
||||
'paragraph text bold italic underline and all subscript superscript code internal link external link…',
|
||||
comments: [],
|
||||
characters: 493,
|
||||
jsonContent: {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'paragraph text ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 1,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'bold',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 2,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'italic',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 8,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'underline',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' and ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 11,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'all',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 32,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'subscript',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 64,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'superscript',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 16,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'code',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'internal link',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'link',
|
||||
version: 2,
|
||||
attributes: {
|
||||
newTab: true,
|
||||
linkType: 'internal',
|
||||
doc: {
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
relationTo: 'text-fields',
|
||||
data: {}, // populated data
|
||||
},
|
||||
text: 'internal link',
|
||||
},
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'external link',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'link',
|
||||
version: 2,
|
||||
attributes: {
|
||||
newTab: true,
|
||||
nofollow: false,
|
||||
url: 'https://fewfwef.de',
|
||||
linkType: 'custom',
|
||||
text: 'external link',
|
||||
},
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ' s. ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 4,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'strikethrough',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'heading 1',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'heading',
|
||||
version: 1,
|
||||
tag: 'h1',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'heading 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'heading',
|
||||
version: 1,
|
||||
tag: 'h2',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'bullet list ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 3',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list',
|
||||
version: 1,
|
||||
listType: 'bullet',
|
||||
start: 1,
|
||||
tag: 'ul',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'ordered list',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 3',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list',
|
||||
version: 1,
|
||||
listType: 'number',
|
||||
start: 1,
|
||||
tag: 'ol',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'check list',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 2',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'item 3',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list',
|
||||
version: 1,
|
||||
listType: 'check',
|
||||
start: 1,
|
||||
tag: 'ul',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'quoteeee',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'quote',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'code block line ',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '1',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
highlightType: 'number',
|
||||
},
|
||||
{
|
||||
type: 'linebreak',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'code block line ',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2',
|
||||
type: 'code-highlight',
|
||||
version: 1,
|
||||
highlightType: 'number',
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'code',
|
||||
version: 1,
|
||||
language: 'javascript',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'Upload:',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
type: 'upload',
|
||||
version: 1,
|
||||
rawImagePayload: {
|
||||
value: {
|
||||
id: '{{UPLOAD_DOC_ID}}',
|
||||
},
|
||||
relationTo: 'uploads',
|
||||
},
|
||||
caption: {
|
||||
editorState: {
|
||||
root: {
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'upload caption',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'root',
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
showCaption: true,
|
||||
data: {}, // populated upload data
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table top left',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 3,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table top right',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablerow',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table bottom left',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: '2x2 table bottom right',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablecell',
|
||||
version: 1,
|
||||
colSpan: 1,
|
||||
rowSpan: 1,
|
||||
backgroundColor: null,
|
||||
headerState: 0,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'tablerow',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'table',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
cells: [
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'kafuj',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'iussu',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
],
|
||||
height: null,
|
||||
id: 'tnied',
|
||||
},
|
||||
{
|
||||
cells: [
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'hpnnv',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'ndteg',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'normal',
|
||||
width: null,
|
||||
},
|
||||
],
|
||||
height: null,
|
||||
id: 'rxyey',
|
||||
},
|
||||
{
|
||||
cells: [
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'rtueq',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'header',
|
||||
width: null,
|
||||
},
|
||||
{
|
||||
colSpan: 1,
|
||||
id: 'vrzoi',
|
||||
json: '{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}',
|
||||
type: 'normal',
|
||||
width: null,
|
||||
},
|
||||
],
|
||||
height: null,
|
||||
id: 'qzglv',
|
||||
},
|
||||
],
|
||||
type: 'tablesheet',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'youtube:',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'youtube',
|
||||
version: 1,
|
||||
videoID: '3Nwt3qu0_UY',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
equation: '3+3',
|
||||
inline: true,
|
||||
type: 'equation',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'collapsible title',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'collapsible-title',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'collabsible conteent',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'collapsible-content',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'collapsible-container',
|
||||
version: 1,
|
||||
open: true,
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
type: 'horizontalrule',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import {
|
||||
lexicalEditor,
|
||||
lexicalHTMLField,
|
||||
LinkFeature,
|
||||
TreeViewFeature,
|
||||
UploadFeature,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
import {
|
||||
LexicalPluginToLexicalFeature,
|
||||
SlateToLexicalFeature,
|
||||
} from '@payloadcms/richtext-lexical/migrate'
|
||||
|
||||
import { lexicalMigrateFieldsSlug } from '../../slugs.js'
|
||||
import { getSimpleLexicalData } from './data.js'
|
||||
|
||||
export const LexicalMigrateFields: CollectionConfig = {
|
||||
slug: lexicalMigrateFieldsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
listSearchableFields: ['title', 'richTextLexicalCustomFields'],
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'lexicalWithLexicalPluginData',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
LexicalPluginToLexicalFeature({ quiet: true }),
|
||||
TreeViewFeature(),
|
||||
LinkFeature({
|
||||
fields: ({ defaultFields }) => [
|
||||
...defaultFields,
|
||||
{
|
||||
name: 'rel',
|
||||
label: 'Rel Attribute',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
options: ['noopener', 'noreferrer', 'nofollow'],
|
||||
admin: {
|
||||
description:
|
||||
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
UploadFeature({
|
||||
collections: {
|
||||
uploads: {
|
||||
fields: [
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor(),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'lexicalWithSlateData',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
SlateToLexicalFeature(),
|
||||
TreeViewFeature(),
|
||||
LinkFeature({
|
||||
fields: ({ defaultFields }) => [
|
||||
...defaultFields,
|
||||
{
|
||||
name: 'rel',
|
||||
label: 'Rel Attribute',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
options: ['noopener', 'noreferrer', 'nofollow'],
|
||||
admin: {
|
||||
description:
|
||||
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
UploadFeature({
|
||||
collections: {
|
||||
uploads: {
|
||||
fields: [
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor(),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'lexicalSimple',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [...defaultFeatures],
|
||||
}),
|
||||
defaultValue: getSimpleLexicalData('simple'),
|
||||
},
|
||||
lexicalHTMLField({ htmlFieldName: 'lexicalSimple_html', lexicalFieldName: 'lexicalSimple' }),
|
||||
{
|
||||
name: 'groupWithLexicalField',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'lexicalInGroupField',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [...defaultFeatures],
|
||||
}),
|
||||
defaultValue: getSimpleLexicalData('group'),
|
||||
},
|
||||
lexicalHTMLField({
|
||||
htmlFieldName: 'lexicalInGroupField_html',
|
||||
lexicalFieldName: 'lexicalInGroupField',
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'arrayWithLexicalField',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'lexicalInArrayField',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [...defaultFeatures],
|
||||
}),
|
||||
},
|
||||
lexicalHTMLField({
|
||||
htmlFieldName: 'lexicalInArrayField_html',
|
||||
lexicalFieldName: 'lexicalInArrayField',
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { payloadPluginLexicalData } from './generatePayloadPluginLexicalData.js'
|
||||
|
||||
export const LexicalRichTextDoc = {
|
||||
title: 'Rich Text',
|
||||
richTextLexicalWithLexicalPluginData: payloadPluginLexicalData,
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { lexicalEditor, UploadFeature } from '@payloadcms/richtext-lexical'
|
||||
|
||||
/**
|
||||
* Do not change this specific CollectionConfig. Simply having this config in payload used to cause the admin panel to hang.
|
||||
* Thus, simply having this config in the test suite is enough to test the bug fix and prevent regressions. In case of regression,
|
||||
* the entire admin panel will hang again and all tests will fail.
|
||||
*/
|
||||
export const LexicalObjectReferenceBugCollection: CollectionConfig = {
|
||||
slug: 'lexicalObjectReferenceBug',
|
||||
fields: [
|
||||
{
|
||||
name: 'lexicalDefault',
|
||||
type: 'richText',
|
||||
},
|
||||
{
|
||||
name: 'lexicalEditor',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: [
|
||||
UploadFeature({
|
||||
collections: {
|
||||
media: {
|
||||
fields: [
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'richText',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import {
|
||||
defaultEditorFeatures,
|
||||
FixedToolbarFeature,
|
||||
lexicalEditor,
|
||||
RelationshipFeature,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { lexicalRelationshipFieldsSlug } from '../../slugs.js'
|
||||
|
||||
export const LexicalRelationshipsFields: CollectionConfig = {
|
||||
slug: lexicalRelationshipFieldsSlug,
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: [
|
||||
...defaultEditorFeatures,
|
||||
RelationshipFeature({
|
||||
enabledCollections: ['array-fields'],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'richText2',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: [...defaultEditorFeatures, RelationshipFeature(), FixedToolbarFeature()],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import type { Block } from 'payload'
|
||||
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
|
||||
export const TextBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
slug: 'textRequired',
|
||||
}
|
||||
|
||||
export const UploadAndRichTextBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
name: 'upload',
|
||||
type: 'upload',
|
||||
relationTo: 'uploads',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor(),
|
||||
},
|
||||
],
|
||||
slug: 'uploadAndRichText',
|
||||
}
|
||||
|
||||
export const RelationshipBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
name: 'rel',
|
||||
type: 'relationship',
|
||||
relationTo: 'uploads',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
slug: 'relationshipBlock',
|
||||
}
|
||||
|
||||
export const SelectFieldBlock: Block = {
|
||||
fields: [
|
||||
{
|
||||
name: 'select',
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: 'option2',
|
||||
},
|
||||
{
|
||||
label: 'Option 3',
|
||||
value: 'option3',
|
||||
},
|
||||
{
|
||||
label: 'Option 4',
|
||||
value: 'option4',
|
||||
},
|
||||
{
|
||||
label: 'Option 5',
|
||||
value: 'option5',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
slug: 'select',
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
import { generateLexicalRichText } from './generateLexicalRichText.js'
|
||||
import { generateSlateRichText } from './generateSlateRichText.js'
|
||||
|
||||
export const richTextBlocks = [
|
||||
{
|
||||
blockType: 'textBlock',
|
||||
text: 'Regular text',
|
||||
},
|
||||
{
|
||||
blockType: 'richTextBlockSlate',
|
||||
text: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Rich text',
|
||||
},
|
||||
],
|
||||
type: 'h1',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
export const richTextDocData = {
|
||||
title: 'Rich Text',
|
||||
selectHasMany: ['one', 'five'],
|
||||
richText: generateSlateRichText(),
|
||||
richTextReadOnly: generateSlateRichText(),
|
||||
richTextCustomFields: generateSlateRichText(),
|
||||
lexicalCustomFields: generateLexicalRichText(),
|
||||
blocks: richTextBlocks,
|
||||
}
|
||||
|
||||
export const richTextBulletsDocData = {
|
||||
title: 'Bullets and Indentation',
|
||||
lexicalCustomFields: generateLexicalRichText(),
|
||||
richText: [
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'I am semantically connected to my sub-bullets',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'I am sub-bullets that are semantically connected to the parent bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Normal bullet',
|
||||
},
|
||||
],
|
||||
type: 'li',
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'I am the old style of sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'Another normal bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'This text precedes a nested list',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'I am a sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'And I am another sub-bullet',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,452 +0,0 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import path from 'path'
|
||||
import { wait } from 'payload/shared'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import {
|
||||
ensureCompilationIsDone,
|
||||
initPageConsoleErrorCatch,
|
||||
saveDocAndAssert,
|
||||
} from '../../../helpers.js'
|
||||
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
|
||||
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
|
||||
import { reInitializeDB } from '../../../helpers/reInitializeDB.js'
|
||||
import { RESTClient } from '../../../helpers/rest.js'
|
||||
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../playwright.config.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const currentFolder = path.dirname(filename)
|
||||
const dirname = path.resolve(currentFolder, '../../')
|
||||
|
||||
const { beforeAll, beforeEach, describe } = test
|
||||
|
||||
let client: RESTClient
|
||||
let page: Page
|
||||
let serverURL: string
|
||||
// If we want to make this run in parallel: test.describe.configure({ mode: 'parallel' })
|
||||
|
||||
describe('Rich Text', () => {
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||
;({ serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||
dirname,
|
||||
}))
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
if (client) {
|
||||
await client.logout()
|
||||
}
|
||||
client = new RESTClient({ defaultSlug: 'users', serverURL })
|
||||
await client.login()
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
|
||||
async function navigateToRichTextFields() {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
|
||||
await page.goto(url.list)
|
||||
|
||||
const linkToDoc = page.locator('.row-1 .cell-title a').first()
|
||||
await expect(() => expect(linkToDoc).toBeTruthy()).toPass({ timeout: POLL_TOPASS_TIMEOUT })
|
||||
const linkDocHref = await linkToDoc.getAttribute('href')
|
||||
|
||||
await linkToDoc.click()
|
||||
|
||||
await page.waitForURL(`**${linkDocHref}`)
|
||||
}
|
||||
|
||||
describe('cell', () => {
|
||||
test('ensure cells are smaller than 300px in height', async () => {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
|
||||
await page.goto(url.list) // Navigate to rich-text list view
|
||||
|
||||
const table = page.locator('.list-controls ~ .table')
|
||||
const lexicalCell = table.locator('.cell-lexicalCustomFields').first()
|
||||
const lexicalHtmlCell = table.locator('.cell-lexicalCustomFields_html').first()
|
||||
const entireRow = table.locator('.row-1').first()
|
||||
|
||||
// Make sure each of the 3 above are no larger than 300px in height:
|
||||
await expect
|
||||
.poll(async () => (await lexicalCell.boundingBox()).height, {
|
||||
timeout: POLL_TOPASS_TIMEOUT,
|
||||
})
|
||||
.toBeLessThanOrEqual(300)
|
||||
await expect
|
||||
.poll(async () => (await lexicalHtmlCell.boundingBox()).height, {
|
||||
timeout: POLL_TOPASS_TIMEOUT,
|
||||
})
|
||||
.toBeLessThanOrEqual(300)
|
||||
await expect
|
||||
.poll(async () => (await entireRow.boundingBox()).height, { timeout: POLL_TOPASS_TIMEOUT })
|
||||
.toBeLessThanOrEqual(300)
|
||||
})
|
||||
})
|
||||
|
||||
describe('toolbar', () => {
|
||||
test('should run url validation', async () => {
|
||||
await navigateToRichTextFields()
|
||||
|
||||
// Open link drawer
|
||||
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click()
|
||||
|
||||
// find the drawer
|
||||
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
||||
await expect(editLinkModal).toBeVisible()
|
||||
|
||||
// Fill values and click Confirm
|
||||
await editLinkModal.locator('#field-text').fill('link text')
|
||||
await editLinkModal.locator('label[for="field-linkType-custom-2"]').click()
|
||||
await editLinkModal.locator('#field-url').fill('')
|
||||
await wait(200)
|
||||
await editLinkModal.locator('button[type="submit"]').click()
|
||||
await wait(400)
|
||||
const errorField = page.locator(
|
||||
'[id^=drawer_1_rich-text-link-] .render-fields > :nth-child(3)',
|
||||
)
|
||||
const hasErrorClass = await errorField.evaluate((el) => el.classList.contains('error'))
|
||||
expect(hasErrorClass).toBe(true)
|
||||
})
|
||||
|
||||
// TODO: Flaky test flakes consistently in CI: https://github.com/payloadcms/payload/actions/runs/8913431889/job/24478995959?pr=6155
|
||||
test.skip('should create new url custom link', async () => {
|
||||
await navigateToRichTextFields()
|
||||
|
||||
// Open link drawer
|
||||
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click()
|
||||
|
||||
// find the drawer
|
||||
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
||||
await expect(editLinkModal).toBeVisible()
|
||||
|
||||
await wait(400)
|
||||
// Fill values and click Confirm
|
||||
await editLinkModal.locator('#field-text').fill('link text')
|
||||
await editLinkModal.locator('label[for="field-linkType-custom-2"]').click()
|
||||
await editLinkModal.locator('#field-url').fill('https://payloadcms.com')
|
||||
await editLinkModal.locator('button[type="submit"]').click()
|
||||
await expect(editLinkModal).toBeHidden()
|
||||
await wait(400)
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
// Remove link from editor body
|
||||
await page.locator('span >> text="link text"').click()
|
||||
const popup = page.locator('.popup--active .rich-text-link__popup')
|
||||
await expect(popup.locator('.rich-text-link__link-label')).toBeVisible()
|
||||
await popup.locator('.rich-text-link__link-close').click()
|
||||
await expect(page.locator('span >> text="link text"')).toHaveCount(0)
|
||||
})
|
||||
|
||||
// TODO: Flaky test flakes consistently in CI: https://github.com/payloadcms/payload/actions/runs/8913769794/job/24480056251?pr=6155
|
||||
test.skip('should create new internal link', async () => {
|
||||
await navigateToRichTextFields()
|
||||
|
||||
// Open link drawer
|
||||
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click()
|
||||
|
||||
// find the drawer
|
||||
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
||||
await expect(editLinkModal).toBeVisible()
|
||||
await wait(400)
|
||||
|
||||
// Fill values and click Confirm
|
||||
await editLinkModal.locator('#field-text').fill('link text')
|
||||
await editLinkModal.locator('label[for="field-linkType-internal-2"]').click()
|
||||
await editLinkModal.locator('#field-doc .rs__control').click()
|
||||
await page.keyboard.type('dev@')
|
||||
await editLinkModal
|
||||
.locator('#field-doc .rs__menu .rs__option:has-text("dev@payloadcms.com")')
|
||||
.click()
|
||||
// await wait(200);
|
||||
await editLinkModal.locator('button[type="submit"]').click()
|
||||
await saveDocAndAssert(page)
|
||||
})
|
||||
|
||||
test('should not create new url link when read only', async () => {
|
||||
await navigateToRichTextFields()
|
||||
const modalTrigger = page.locator('.rich-text--read-only .rich-text__toolbar button .link')
|
||||
await expect(modalTrigger).toBeDisabled()
|
||||
})
|
||||
|
||||
test('should only list RTE enabled upload collections in drawer', async () => {
|
||||
await navigateToRichTextFields()
|
||||
await wait(1000)
|
||||
|
||||
// Open link drawer
|
||||
await page
|
||||
.locator('.rich-text__toolbar button:not([disabled]) .upload-rich-text-button')
|
||||
.first()
|
||||
.click()
|
||||
|
||||
const drawer = page.locator('[id^=list-drawer_1_]')
|
||||
await expect(drawer).toBeVisible()
|
||||
|
||||
// open the list select menu
|
||||
await page.locator('.list-drawer__select-collection-wrap .rs__control').click()
|
||||
|
||||
const menu = page.locator('.list-drawer__select-collection-wrap .rs__menu')
|
||||
// `uploads-3` has enableRichTextRelationship set to false
|
||||
await expect(menu).not.toContainText('Uploads3')
|
||||
})
|
||||
|
||||
// TODO: this test can't find the selector for the search filter, but functionality works.
|
||||
// Need to debug
|
||||
test.skip('should search correct useAsTitle field after toggling collection in list drawer', async () => {
|
||||
await navigateToRichTextFields()
|
||||
|
||||
// open link drawer
|
||||
const field = page.locator('#field-richText')
|
||||
const button = field.locator(
|
||||
'button.rich-text-relationship__list-drawer-toggler.list-drawer__toggler',
|
||||
)
|
||||
await button.click()
|
||||
|
||||
// check that the search is on the `name` field of the `text-fields` collection
|
||||
const drawer = page.locator('[id^=list-drawer_1_]')
|
||||
|
||||
await expect(drawer.locator('.search-filter__input')).toHaveAttribute(
|
||||
'placeholder',
|
||||
'Search by Text',
|
||||
)
|
||||
|
||||
// change the selected collection to `array-fields`
|
||||
await page.locator('.list-drawer_select-collection-wrap .rs__control').click()
|
||||
const menu = page.locator('.list-drawer__select-collection-wrap .rs__menu')
|
||||
await menu.locator('.rs__option').getByText('Array Field').click()
|
||||
|
||||
// check that `id` is now the default search field
|
||||
await expect(drawer.locator('.search-filter__input')).toHaveAttribute(
|
||||
'placeholder',
|
||||
'Search by ID',
|
||||
)
|
||||
})
|
||||
|
||||
test('should only list RTE enabled collections in link drawer', async () => {
|
||||
await navigateToRichTextFields()
|
||||
await wait(1000)
|
||||
|
||||
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click()
|
||||
|
||||
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
||||
await expect(editLinkModal).toBeVisible()
|
||||
|
||||
await wait(1000)
|
||||
|
||||
await editLinkModal.locator('label[for="field-linkType-internal-2"]').click()
|
||||
await editLinkModal.locator('.relationship__wrap .rs__control').click()
|
||||
|
||||
const menu = page.locator('.relationship__wrap .rs__menu')
|
||||
|
||||
// array-fields has enableRichTextLink set to false
|
||||
await expect(menu).not.toContainText('Array Fields')
|
||||
})
|
||||
|
||||
test('should only list non-upload collections in relationship drawer', async () => {
|
||||
await navigateToRichTextFields()
|
||||
await wait(1000)
|
||||
|
||||
// Open link drawer
|
||||
await page
|
||||
.locator('.rich-text__toolbar button:not([disabled]) .relationship-rich-text-button')
|
||||
.first()
|
||||
.click()
|
||||
|
||||
await wait(1000)
|
||||
|
||||
// open the list select menu
|
||||
await page.locator('.list-drawer__select-collection-wrap .rs__control').click()
|
||||
|
||||
const menu = page.locator('.list-drawer__select-collection-wrap .rs__menu')
|
||||
const regex = /\bUploads\b/
|
||||
await expect(menu).not.toContainText(regex)
|
||||
})
|
||||
|
||||
// TODO: Flaky test in CI. Flake: https://github.com/payloadcms/payload/actions/runs/8914532814/job/24482407114
|
||||
test.skip('should respect customizing the default fields', async () => {
|
||||
const linkText = 'link'
|
||||
const value = 'test value'
|
||||
await navigateToRichTextFields()
|
||||
await wait(1000)
|
||||
|
||||
const field = page.locator('.rich-text', {
|
||||
has: page.locator('#field-richTextCustomFields'),
|
||||
})
|
||||
// open link drawer
|
||||
const button = field.locator('button.rich-text__button.link')
|
||||
await button.click()
|
||||
await wait(1000)
|
||||
|
||||
// fill link fields
|
||||
const linkDrawer = page.locator('[id^=drawer_1_rich-text-link-]')
|
||||
const fields = linkDrawer.locator('.render-fields > .field-type')
|
||||
await fields.locator('#field-text').fill(linkText)
|
||||
await fields.locator('#field-url').fill('https://payloadcms.com')
|
||||
const input = fields.locator('#field-fields__customLinkField')
|
||||
await input.fill(value)
|
||||
|
||||
await wait(1000)
|
||||
|
||||
// submit link closing drawer
|
||||
await linkDrawer.locator('button[type="submit"]').click()
|
||||
const linkInEditor = field.locator(`.rich-text-link >> text="${linkText}"`)
|
||||
await wait(300)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
// open modal again
|
||||
await linkInEditor.click()
|
||||
|
||||
const popup = page.locator('.popup--active .rich-text-link__popup')
|
||||
await expect(popup).toBeVisible()
|
||||
|
||||
await popup.locator('.rich-text-link__link-edit').click()
|
||||
|
||||
const linkDrawer2 = page.locator('[id^=drawer_1_rich-text-link-]')
|
||||
const fields2 = linkDrawer2.locator('.render-fields > .field-type')
|
||||
const input2 = fields2.locator('#field-fields__customLinkField')
|
||||
|
||||
await expect(input2).toHaveValue(value)
|
||||
})
|
||||
})
|
||||
|
||||
describe('editor', () => {
|
||||
test('should populate url link', async () => {
|
||||
await navigateToRichTextFields()
|
||||
await wait(500)
|
||||
|
||||
// Open link popup
|
||||
await page.locator('#field-richText span >> text="render links"').click()
|
||||
const popup = page.locator('.popup--active .rich-text-link__popup')
|
||||
await expect(popup).toBeVisible()
|
||||
await expect(popup.locator('a')).toHaveAttribute('href', 'https://payloadcms.com')
|
||||
|
||||
// Open the drawer
|
||||
await popup.locator('.rich-text-link__link-edit').click()
|
||||
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
||||
await expect(editLinkModal).toBeVisible()
|
||||
|
||||
// Check the drawer values
|
||||
const textField = editLinkModal.locator('#field-text')
|
||||
await expect(textField).toHaveValue('render links')
|
||||
|
||||
await wait(1000)
|
||||
// Close the drawer
|
||||
await editLinkModal.locator('button[type="submit"]').click()
|
||||
await expect(editLinkModal).toBeHidden()
|
||||
})
|
||||
|
||||
test('should populate relationship link', async () => {
|
||||
await navigateToRichTextFields()
|
||||
|
||||
// Open link popup
|
||||
await page.locator('#field-richText span >> text="link to relationships"').click()
|
||||
const popup = page.locator('.popup--active .rich-text-link__popup')
|
||||
await expect(popup).toBeVisible()
|
||||
await expect(popup.locator('a')).toHaveAttribute(
|
||||
'href',
|
||||
/\/admin\/collections\/array-fields\/.*/,
|
||||
)
|
||||
|
||||
// Open the drawer
|
||||
await popup.locator('.rich-text-link__link-edit').click()
|
||||
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
||||
await expect(editLinkModal).toBeVisible()
|
||||
|
||||
// Check the drawer values
|
||||
const textField = editLinkModal.locator('#field-text')
|
||||
await expect(textField).toHaveValue('link to relationships')
|
||||
})
|
||||
|
||||
test('should open upload drawer and render custom relationship fields', async () => {
|
||||
await navigateToRichTextFields()
|
||||
const field = page.locator('#field-richText')
|
||||
const button = field.locator('button.rich-text-upload__upload-drawer-toggler')
|
||||
|
||||
await button.click()
|
||||
|
||||
const documentDrawer = page.locator('[id^=drawer_1_upload-drawer-]')
|
||||
await expect(documentDrawer).toBeVisible()
|
||||
const caption = documentDrawer.locator('#field-caption')
|
||||
await expect(caption).toBeVisible()
|
||||
})
|
||||
|
||||
test('should open upload document drawer from read-only field', async () => {
|
||||
await navigateToRichTextFields()
|
||||
const field = page.locator('#field-richTextReadOnly')
|
||||
const button = field.locator(
|
||||
'button.rich-text-upload__doc-drawer-toggler.doc-drawer__toggler',
|
||||
)
|
||||
|
||||
await button.click()
|
||||
|
||||
const documentDrawer = page.locator('[id^=doc-drawer_uploads_1_]')
|
||||
await expect(documentDrawer).toBeVisible()
|
||||
})
|
||||
|
||||
test('should open relationship document drawer from read-only field', async () => {
|
||||
await navigateToRichTextFields()
|
||||
const field = page.locator('#field-richTextReadOnly')
|
||||
const button = field.locator(
|
||||
'button.rich-text-relationship__doc-drawer-toggler.doc-drawer__toggler',
|
||||
)
|
||||
|
||||
await button.click()
|
||||
|
||||
const documentDrawer = page.locator('[id^=doc-drawer_text-fields_1_]')
|
||||
await expect(documentDrawer).toBeVisible()
|
||||
})
|
||||
|
||||
test('should populate new links', async () => {
|
||||
await navigateToRichTextFields()
|
||||
await wait(1000)
|
||||
|
||||
// Highlight existing text
|
||||
const headingElement = page.locator(
|
||||
'#field-richText h1 >> text="Hello, I\'m a rich text field."',
|
||||
)
|
||||
await headingElement.selectText()
|
||||
|
||||
await wait(500)
|
||||
|
||||
// click the toolbar link button
|
||||
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click()
|
||||
|
||||
// find the drawer and confirm the values
|
||||
const editLinkModal = page.locator('[id^=drawer_1_rich-text-link-]')
|
||||
await expect(editLinkModal).toBeVisible()
|
||||
const textField = editLinkModal.locator('#field-text')
|
||||
await expect(textField).toHaveValue("Hello, I'm a rich text field.")
|
||||
})
|
||||
test('should not take value from previous block', async () => {
|
||||
await navigateToRichTextFields()
|
||||
await page.locator('#field-blocks').scrollIntoViewIfNeeded()
|
||||
await expect(page.locator('#field-blocks__0__text')).toBeVisible()
|
||||
await expect(page.locator('#field-blocks__0__text')).toHaveValue('Regular text')
|
||||
await wait(500)
|
||||
const editBlock = page.locator('#blocks-row-0 .popup-button')
|
||||
await editBlock.click()
|
||||
const removeButton = page.locator('#blocks-row-0').getByRole('button', { name: 'Remove' })
|
||||
await expect(removeButton).toBeVisible()
|
||||
await wait(500)
|
||||
await removeButton.click()
|
||||
const richTextField = page.locator('#field-blocks__0__text')
|
||||
await expect(richTextField).toBeVisible()
|
||||
const richTextValue = await richTextField.innerText()
|
||||
expect(richTextValue).toContain('Rich text')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,318 +0,0 @@
|
||||
import { textFieldsSlug } from '../../slugs.js'
|
||||
import { loremIpsum } from './loremIpsum.js'
|
||||
|
||||
export function generateLexicalRichText() {
|
||||
return {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: "Hello, I'm a rich text field.",
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: 'center',
|
||||
indent: 0,
|
||||
type: 'heading',
|
||||
version: 1,
|
||||
tag: 'h1',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'I can do all kinds of fun stuff like ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'render links',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
id: '665d10938106ab380c7f3730',
|
||||
type: 'link',
|
||||
version: 2,
|
||||
fields: {
|
||||
url: 'https://payloadcms.com',
|
||||
newTab: true,
|
||||
linkType: 'custom',
|
||||
},
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ', ',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'link to relationships',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
id: '665d10938106ab380c7f3730',
|
||||
type: 'link',
|
||||
version: 2,
|
||||
fields: {
|
||||
url: 'https://',
|
||||
doc: {
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
relationTo: textFieldsSlug,
|
||||
},
|
||||
newTab: false,
|
||||
linkType: 'internal',
|
||||
},
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: ', and store nested relationship fields:',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 2,
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
relationTo: 'text-fields',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'You can build your own elements, too.',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: "It's built with Lexical",
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'It stores content as JSON so you can use it wherever you need',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: "It's got a great editing experience for non-technical users",
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem',
|
||||
version: 1,
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list',
|
||||
version: 1,
|
||||
listType: 'bullet',
|
||||
start: 1,
|
||||
tag: 'ul',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'And a whole lot more.',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
type: 'upload',
|
||||
version: 2,
|
||||
id: '665d10938106ab380c7f372f',
|
||||
relationTo: 'uploads',
|
||||
value: '{{UPLOAD_DOC_ID}}',
|
||||
fields: {
|
||||
caption: {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
...[...Array(4)].map(() => ({
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: loremIpsum,
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
})),
|
||||
],
|
||||
direction: 'ltr',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam hendrerit nisi sed sollicitudin pellentesque. Nunc posuere purus rhoncus pulvinar aliquam. Ut aliquet tristique nisl vitae volutpat. Nulla aliquet porttitor venenatis. Donec a dui et dui fringilla consectetur id nec massa. Aliquam erat volutpat. Sed ut dui ut lacus dictum fermentum vel tincidunt neque. Sed sed lacinia lectus. Duis sit amet sodales felis. Duis nunc eros, mattis at dui ac, convallis semper risus. In adipiscing ultrices tellus, in suscipit massa vehicula eu.',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam hendrerit nisi sed sollicitudin pellentesque. Nunc posuere purus rhoncus pulvinar aliquam. Ut aliquet tristique nisl vitae volutpat. Nulla aliquet porttitor venenatis. Donec a dui et dui fringilla consectetur id nec massa. Aliquam erat volutpat. Sed ut dui ut lacus dictum fermentum vel tincidunt neque. Sed sed lacinia lectus. Duis sit amet sodales felis. Duis nunc eros, mattis at dui ac, convallis semper risus. In adipiscing ultrices tellus, in suscipit massa vehicula eu.',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import { loremIpsum } from './loremIpsum.js'
|
||||
|
||||
export function generateSlateRichText() {
|
||||
return [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: "Hello, I'm a rich text field.",
|
||||
},
|
||||
],
|
||||
type: 'h1',
|
||||
textAlign: 'center',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'I can do all kinds of fun stuff like ',
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
url: 'https://payloadcms.com',
|
||||
newTab: true,
|
||||
children: [
|
||||
{
|
||||
text: 'render links',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: ', ',
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
linkType: 'internal',
|
||||
doc: {
|
||||
value: '{{ARRAY_DOC_ID}}',
|
||||
relationTo: 'array-fields',
|
||||
},
|
||||
fields: {},
|
||||
children: [
|
||||
{
|
||||
text: 'link to relationships',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: ', and store nested relationship fields:',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
},
|
||||
],
|
||||
type: 'relationship',
|
||||
value: {
|
||||
id: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
relationTo: 'text-fields',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'You can build your own elements, too.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ul',
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: "It's built with SlateJS",
|
||||
},
|
||||
],
|
||||
type: 'li',
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: 'It stores content as JSON so you can use it wherever you need',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'li',
|
||||
children: [
|
||||
{
|
||||
text: "It's got a great editing experience for non-technical users",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'And a whole lot more.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
},
|
||||
],
|
||||
type: 'upload',
|
||||
value: {
|
||||
id: '{{UPLOAD_DOC_ID}}',
|
||||
},
|
||||
relationTo: 'uploads',
|
||||
fields: {
|
||||
caption: [
|
||||
...[...Array(4)].map(() => {
|
||||
return {
|
||||
children: [
|
||||
{
|
||||
text: loremIpsum,
|
||||
},
|
||||
],
|
||||
}
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
...[...Array(2)].map(() => {
|
||||
return {
|
||||
children: [
|
||||
{
|
||||
text: loremIpsum,
|
||||
},
|
||||
],
|
||||
}
|
||||
}),
|
||||
]
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import {
|
||||
BlocksFeature,
|
||||
HTMLConverterFeature,
|
||||
lexicalEditor,
|
||||
lexicalHTML,
|
||||
LinkFeature,
|
||||
TreeViewFeature,
|
||||
UploadFeature,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
|
||||
import { richTextFieldsSlug } from '../../slugs.js'
|
||||
import { RelationshipBlock, SelectFieldBlock, TextBlock, UploadAndRichTextBlock } from './blocks.js'
|
||||
|
||||
const RichTextFields: CollectionConfig = {
|
||||
slug: richTextFieldsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'lexicalCustomFields',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
TreeViewFeature(),
|
||||
HTMLConverterFeature({}),
|
||||
LinkFeature({
|
||||
fields: ({ defaultFields }) => [
|
||||
...defaultFields,
|
||||
{
|
||||
name: 'rel',
|
||||
label: 'Rel Attribute',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
options: ['noopener', 'noreferrer', 'nofollow'],
|
||||
admin: {
|
||||
description:
|
||||
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
UploadFeature({
|
||||
collections: {
|
||||
uploads: {
|
||||
fields: [
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor(),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
BlocksFeature({
|
||||
blocks: [TextBlock, UploadAndRichTextBlock, SelectFieldBlock, RelationshipBlock],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
lexicalHTML('lexicalCustomFields', { name: 'lexicalCustomFields_html' }),
|
||||
{
|
||||
name: 'lexical',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
description: 'This rich text field uses the lexical editor.',
|
||||
},
|
||||
defaultValue: {
|
||||
root: {
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'This is a paragraph.',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: null,
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'root',
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [...defaultFeatures, TreeViewFeature()],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'selectHasMany',
|
||||
hasMany: true,
|
||||
type: 'select',
|
||||
admin: {
|
||||
description:
|
||||
'This select field is rendered here to ensure its options dropdown renders above the rich text toolbar.',
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: 'Value One',
|
||||
value: 'one',
|
||||
},
|
||||
{
|
||||
label: 'Value Two',
|
||||
value: 'two',
|
||||
},
|
||||
{
|
||||
label: 'Value Three',
|
||||
value: 'three',
|
||||
},
|
||||
{
|
||||
label: 'Value Four',
|
||||
value: 'four',
|
||||
},
|
||||
{
|
||||
label: 'Value Five',
|
||||
value: 'five',
|
||||
},
|
||||
{
|
||||
label: 'Value Six',
|
||||
value: 'six',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: slateEditor({
|
||||
admin: {
|
||||
elements: [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'ul',
|
||||
'ol',
|
||||
'textAlign',
|
||||
'indent',
|
||||
'link',
|
||||
'relationship',
|
||||
'upload',
|
||||
],
|
||||
link: {
|
||||
fields: [
|
||||
{
|
||||
name: 'rel',
|
||||
label: 'Rel Attribute',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
options: ['noopener', 'noreferrer', 'nofollow'],
|
||||
admin: {
|
||||
description:
|
||||
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
upload: {
|
||||
collections: {
|
||||
uploads: {
|
||||
fields: [
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'richText',
|
||||
editor: slateEditor({}),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'richTextCustomFields',
|
||||
type: 'richText',
|
||||
editor: slateEditor({
|
||||
admin: {
|
||||
elements: [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'ul',
|
||||
'ol',
|
||||
'indent',
|
||||
'link',
|
||||
'relationship',
|
||||
'upload',
|
||||
],
|
||||
link: {
|
||||
fields: ({ defaultFields }) => {
|
||||
return [
|
||||
...defaultFields,
|
||||
{
|
||||
label: 'Custom',
|
||||
name: 'customLinkField',
|
||||
type: 'text',
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
upload: {
|
||||
collections: {
|
||||
uploads: {
|
||||
fields: [
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'richText',
|
||||
editor: slateEditor({}),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'richTextReadOnly',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
editor: slateEditor({
|
||||
admin: {
|
||||
elements: [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'ul',
|
||||
'ol',
|
||||
'indent',
|
||||
'link',
|
||||
'relationship',
|
||||
'upload',
|
||||
],
|
||||
link: {
|
||||
fields: [
|
||||
{
|
||||
name: 'rel',
|
||||
label: 'Rel Attribute',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
options: ['noopener', 'noreferrer', 'nofollow'],
|
||||
admin: {
|
||||
description:
|
||||
'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
upload: {
|
||||
collections: {
|
||||
uploads: {
|
||||
fields: [
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'richText',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'textBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'richTextBlockSlate',
|
||||
fields: [
|
||||
{
|
||||
editor: slateEditor({}),
|
||||
name: 'text',
|
||||
type: 'richText',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default RichTextFields
|
||||
@@ -1,2 +0,0 @@
|
||||
export const loremIpsum =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam hendrerit nisi sed sollicitudin pellentesque. Nunc posuere purus rhoncus pulvinar aliquam. Ut aliquet tristique nisl vitae volutpat. Nulla aliquet porttitor venenatis. Donec a dui et dui fringilla consectetur id nec massa. Aliquam erat volutpat. Sed ut dui ut lacus dictum fermentum vel tincidunt neque. Sed sed lacinia lectus. Duis sit amet sodales felis. Duis nunc eros, mattis at dui ac, convallis semper risus. In adipiscing ultrices tellus, in suscipit massa vehicula eu.'
|
||||
Reference in New Issue
Block a user