fix(live-preview): populates rte uploads and relationships (#4379)
This commit is contained in:
@@ -53,13 +53,20 @@ export const traverseRichText = ({
|
|||||||
? Array.isArray(incomingData[key])
|
? Array.isArray(incomingData[key])
|
||||||
? []
|
? []
|
||||||
: {}
|
: {}
|
||||||
: incomingData[key]
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const isRelationship = key === 'value' && 'relationTo' in incomingData
|
const isRelationship = key === 'value' && 'relationTo' in incomingData
|
||||||
|
|
||||||
if (isRelationship) {
|
if (isRelationship) {
|
||||||
const needsPopulation = !result.value || typeof result.value !== 'object'
|
// or if there are no keys besides id
|
||||||
|
const needsPopulation =
|
||||||
|
!result.value ||
|
||||||
|
typeof result.value !== 'object' ||
|
||||||
|
(typeof result.value === 'object' &&
|
||||||
|
Object.keys(result.value).length === 1 &&
|
||||||
|
'id' in result.value)
|
||||||
|
|
||||||
const hasChanged =
|
const hasChanged =
|
||||||
result &&
|
result &&
|
||||||
typeof result === 'object' &&
|
typeof result === 'object' &&
|
||||||
@@ -71,7 +78,10 @@ export const traverseRichText = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
populationsByCollection[incomingData.relationTo].push({
|
populationsByCollection[incomingData.relationTo].push({
|
||||||
id: incomingData[key],
|
id:
|
||||||
|
incomingData[key] && typeof incomingData[key] === 'object'
|
||||||
|
? incomingData[key].id
|
||||||
|
: incomingData[key],
|
||||||
accessor: 'value',
|
accessor: 'value',
|
||||||
ref: result,
|
ref: result,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
import type { CollectionConfig } from '../../../packages/payload/src/collections/config/types'
|
||||||
|
|
||||||
|
import { lexicalEditor } from '../../../packages/richtext-lexical/src'
|
||||||
import { Archive } from '../blocks/ArchiveBlock'
|
import { Archive } from '../blocks/ArchiveBlock'
|
||||||
import { CallToAction } from '../blocks/CallToAction'
|
import { CallToAction } from '../blocks/CallToAction'
|
||||||
import { Content } from '../blocks/Content'
|
import { Content } from '../blocks/Content'
|
||||||
@@ -62,8 +63,15 @@ export const Pages: CollectionConfig = {
|
|||||||
label: 'Test',
|
label: 'Test',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'relationshipInRichText',
|
label: 'Rich Text — Slate',
|
||||||
type: 'richText',
|
type: 'richText',
|
||||||
|
name: 'richTextSlate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Rich Text — Lexical',
|
||||||
|
type: 'richText',
|
||||||
|
name: 'richTextLexical',
|
||||||
|
editor: lexicalEditor({}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'relationshipAsUpload',
|
name: 'relationshipAsUpload',
|
||||||
|
|||||||
@@ -154,10 +154,7 @@ describe('Collections - Live Preview', () => {
|
|||||||
expect(mergedData._numberOfRequests).toEqual(0)
|
expect(mergedData._numberOfRequests).toEqual(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: this test is not working in Postgres
|
it('— uploads - adds and removes media', async () => {
|
||||||
// This is because of how relationships are handled in `mergeData`
|
|
||||||
// This test passes in MongoDB, though
|
|
||||||
it.skip('— uploads - adds and removes media', async () => {
|
|
||||||
const initialData: Partial<Page> = {
|
const initialData: Partial<Page> = {
|
||||||
title: 'Test Page',
|
title: 'Test Page',
|
||||||
}
|
}
|
||||||
@@ -198,6 +195,166 @@ describe('Collections - Live Preview', () => {
|
|||||||
|
|
||||||
expect(mergedDataWithoutUpload.hero.media).toBeFalsy()
|
expect(mergedDataWithoutUpload.hero.media).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('— uploads - populates within Slate rich text editor', async () => {
|
||||||
|
const initialData: Partial<Page> = {
|
||||||
|
title: 'Test Page',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add upload
|
||||||
|
const merge1 = await mergeData({
|
||||||
|
depth: 1,
|
||||||
|
fieldSchema: schemaJSON,
|
||||||
|
incomingData: {
|
||||||
|
...initialData,
|
||||||
|
richTextSlate: [
|
||||||
|
{
|
||||||
|
type: 'upload',
|
||||||
|
relationTo: 'media',
|
||||||
|
value: media.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
initialData,
|
||||||
|
serverURL,
|
||||||
|
returnNumberOfRequests: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(merge1.richTextSlate).toHaveLength(1)
|
||||||
|
expect(merge1.richTextSlate[0].value).toMatchObject(media)
|
||||||
|
expect(merge1._numberOfRequests).toEqual(1)
|
||||||
|
|
||||||
|
// Remove upload
|
||||||
|
const merge2 = await mergeData({
|
||||||
|
depth: 1,
|
||||||
|
fieldSchema: schemaJSON,
|
||||||
|
incomingData: {
|
||||||
|
...merge1,
|
||||||
|
richTextSlate: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: 'Hello, world!',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
initialData,
|
||||||
|
serverURL,
|
||||||
|
returnNumberOfRequests: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(merge2.richTextSlate).toHaveLength(1)
|
||||||
|
expect(merge2.richTextSlate[0].value).toBeFalsy()
|
||||||
|
expect(merge2.richTextSlate[0].type).toEqual('paragraph')
|
||||||
|
expect(merge2._numberOfRequests).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('— uploads - populates within Lexical rich text editor', async () => {
|
||||||
|
const initialData: Partial<Page> = {
|
||||||
|
title: 'Test Page',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add upload
|
||||||
|
const merge1 = await mergeData({
|
||||||
|
depth: 1,
|
||||||
|
fieldSchema: schemaJSON,
|
||||||
|
incomingData: {
|
||||||
|
...initialData,
|
||||||
|
richTextLexical: {
|
||||||
|
root: {
|
||||||
|
type: 'root',
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
version: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
detail: 0,
|
||||||
|
format: 0,
|
||||||
|
mode: 'normal',
|
||||||
|
style: '',
|
||||||
|
text: 'Hello, world!',
|
||||||
|
type: 'text',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
direction: 'ltr',
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
type: 'paragraph',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: '',
|
||||||
|
type: 'upload',
|
||||||
|
relationTo: 'media',
|
||||||
|
version: 1,
|
||||||
|
value: media.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
direction: 'ltr',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
initialData,
|
||||||
|
serverURL,
|
||||||
|
returnNumberOfRequests: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(merge1.richTextLexical.root.children).toHaveLength(2)
|
||||||
|
expect(merge1.richTextLexical.root.children[1].value).toMatchObject(media)
|
||||||
|
expect(merge1._numberOfRequests).toEqual(1)
|
||||||
|
|
||||||
|
// Remove upload
|
||||||
|
const merge2 = await mergeData({
|
||||||
|
depth: 1,
|
||||||
|
fieldSchema: schemaJSON,
|
||||||
|
incomingData: {
|
||||||
|
...merge1,
|
||||||
|
richTextLexical: {
|
||||||
|
root: {
|
||||||
|
type: 'root',
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
version: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
detail: 0,
|
||||||
|
format: 0,
|
||||||
|
mode: 'normal',
|
||||||
|
style: '',
|
||||||
|
text: 'Hello, world!',
|
||||||
|
type: 'text',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
direction: 'ltr',
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
type: 'paragraph',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
direction: 'ltr',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
initialData,
|
||||||
|
serverURL,
|
||||||
|
returnNumberOfRequests: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(merge2.richTextLexical.root.children).toHaveLength(1)
|
||||||
|
expect(merge2.richTextLexical.root.children[0].value).toBeFalsy()
|
||||||
|
expect(merge2.richTextLexical.root.children[0].type).toEqual('paragraph')
|
||||||
|
})
|
||||||
|
|
||||||
it('— relationships - populates monomorphic has one relationships', async () => {
|
it('— relationships - populates monomorphic has one relationships', async () => {
|
||||||
const initialData: Partial<Page> = {
|
const initialData: Partial<Page> = {
|
||||||
title: 'Test Page',
|
title: 'Test Page',
|
||||||
@@ -422,7 +579,7 @@ describe('Collections - Live Preview', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
initialData,
|
initialData: merge1,
|
||||||
serverURL,
|
serverURL,
|
||||||
returnNumberOfRequests: true,
|
returnNumberOfRequests: true,
|
||||||
})
|
})
|
||||||
@@ -451,7 +608,130 @@ describe('Collections - Live Preview', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('— relationships - populates within rich text', async () => {
|
it('— relationships - populates within Slate rich text editor', async () => {
|
||||||
|
const initialData: Partial<Page> = {
|
||||||
|
title: 'Test Page',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a relationship and an upload
|
||||||
|
const merge1 = await mergeData({
|
||||||
|
depth: 1,
|
||||||
|
fieldSchema: schemaJSON,
|
||||||
|
incomingData: {
|
||||||
|
...initialData,
|
||||||
|
richTextSlate: [
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: ' ',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
relationTo: 'posts',
|
||||||
|
type: 'relationship',
|
||||||
|
value: {
|
||||||
|
id: testPost.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
relationTo: 'media',
|
||||||
|
type: 'upload',
|
||||||
|
value: {
|
||||||
|
id: media.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
initialData,
|
||||||
|
serverURL,
|
||||||
|
returnNumberOfRequests: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(merge1._numberOfRequests).toEqual(2)
|
||||||
|
expect(merge1.richTextSlate).toHaveLength(3)
|
||||||
|
expect(merge1.richTextSlate[0].type).toEqual('relationship')
|
||||||
|
expect(merge1.richTextSlate[0].value).toMatchObject(testPost)
|
||||||
|
expect(merge1.richTextSlate[1].type).toEqual('paragraph')
|
||||||
|
expect(merge1.richTextSlate[2].type).toEqual('upload')
|
||||||
|
expect(merge1.richTextSlate[2].value).toMatchObject(media)
|
||||||
|
|
||||||
|
// Add a new node between the relationship and the upload
|
||||||
|
const merge2 = await mergeData({
|
||||||
|
depth: 1,
|
||||||
|
fieldSchema: schemaJSON,
|
||||||
|
incomingData: {
|
||||||
|
...merge1,
|
||||||
|
richTextSlate: [
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: ' ',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
relationTo: 'posts',
|
||||||
|
type: 'relationship',
|
||||||
|
value: {
|
||||||
|
id: testPost.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
relationTo: 'media',
|
||||||
|
type: 'upload',
|
||||||
|
value: {
|
||||||
|
id: media.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
initialData: merge1,
|
||||||
|
serverURL,
|
||||||
|
returnNumberOfRequests: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(merge2._numberOfRequests).toEqual(1)
|
||||||
|
expect(merge2.richTextSlate).toHaveLength(4)
|
||||||
|
expect(merge2.richTextSlate[0].type).toEqual('relationship')
|
||||||
|
expect(merge2.richTextSlate[0].value).toMatchObject(testPost)
|
||||||
|
expect(merge2.richTextSlate[1].type).toEqual('paragraph')
|
||||||
|
expect(merge2.richTextSlate[2].type).toEqual('paragraph')
|
||||||
|
expect(merge2.richTextSlate[3].type).toEqual('upload')
|
||||||
|
expect(merge2.richTextSlate[3].value).toMatchObject(media)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('— relationships - populates within Lexical rich text editor', async () => {
|
||||||
const initialData: Partial<Page> = {
|
const initialData: Partial<Page> = {
|
||||||
title: 'Test Page',
|
title: 'Test Page',
|
||||||
}
|
}
|
||||||
@@ -462,56 +742,130 @@ describe('Collections - Live Preview', () => {
|
|||||||
fieldSchema: schemaJSON,
|
fieldSchema: schemaJSON,
|
||||||
incomingData: {
|
incomingData: {
|
||||||
...initialData,
|
...initialData,
|
||||||
relationshipInRichText: [
|
richTextLexical: {
|
||||||
{
|
root: {
|
||||||
type: 'paragraph',
|
type: 'root',
|
||||||
text: 'Paragraph 1',
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
version: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
format: '',
|
||||||
|
type: 'relationship',
|
||||||
|
version: 1,
|
||||||
|
relationTo: 'posts',
|
||||||
|
value: {
|
||||||
|
id: testPost.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: [],
|
||||||
|
direction: null,
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
type: 'paragraph',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: '',
|
||||||
|
type: 'upload',
|
||||||
|
version: 1,
|
||||||
|
fields: null,
|
||||||
|
relationTo: 'media',
|
||||||
|
value: {
|
||||||
|
id: media.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
direction: null,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
type: 'reference',
|
|
||||||
reference: {
|
|
||||||
relationTo: 'posts',
|
|
||||||
value: testPost.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
initialData,
|
initialData,
|
||||||
serverURL,
|
serverURL,
|
||||||
returnNumberOfRequests: true,
|
returnNumberOfRequests: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(merge1._numberOfRequests).toEqual(1)
|
expect(merge1._numberOfRequests).toEqual(2)
|
||||||
expect(merge1.relationshipInRichText).toHaveLength(2)
|
expect(merge1.richTextLexical.root.children).toHaveLength(3)
|
||||||
expect(merge1.relationshipInRichText[1].reference.value).toMatchObject(testPost)
|
expect(merge1.richTextLexical.root.children[0].type).toEqual('relationship')
|
||||||
|
expect(merge1.richTextLexical.root.children[0].value).toMatchObject(testPost)
|
||||||
|
expect(merge1.richTextLexical.root.children[1].type).toEqual('paragraph')
|
||||||
|
expect(merge1.richTextLexical.root.children[2].type).toEqual('upload')
|
||||||
|
expect(merge1.richTextLexical.root.children[2].value).toMatchObject(media)
|
||||||
|
|
||||||
// Remove the relationship
|
// Add a node before the populated one
|
||||||
const merge2 = await mergeData({
|
const merge2 = await mergeData({
|
||||||
depth: 1,
|
depth: 1,
|
||||||
fieldSchema: schemaJSON,
|
fieldSchema: schemaJSON,
|
||||||
incomingData: {
|
incomingData: {
|
||||||
...merge1,
|
...merge1,
|
||||||
relationshipInRichText: [
|
richTextLexical: {
|
||||||
{
|
root: {
|
||||||
type: 'paragraph',
|
type: 'root',
|
||||||
text: 'Paragraph 1',
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
version: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
format: '',
|
||||||
|
type: 'relationship',
|
||||||
|
version: 1,
|
||||||
|
relationTo: 'posts',
|
||||||
|
value: {
|
||||||
|
id: testPost.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: [],
|
||||||
|
direction: null,
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
type: 'paragraph',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: [],
|
||||||
|
direction: null,
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
type: 'paragraph',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: '',
|
||||||
|
type: 'upload',
|
||||||
|
version: 1,
|
||||||
|
fields: null,
|
||||||
|
relationTo: 'media',
|
||||||
|
value: {
|
||||||
|
id: media.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
direction: null,
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
initialData,
|
initialData: merge1,
|
||||||
serverURL,
|
serverURL,
|
||||||
returnNumberOfRequests: true,
|
returnNumberOfRequests: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(merge2._numberOfRequests).toEqual(0)
|
expect(merge2._numberOfRequests).toEqual(1)
|
||||||
expect(merge2.relationshipInRichText).toHaveLength(1)
|
expect(merge2.richTextLexical.root.children).toHaveLength(4)
|
||||||
expect(merge2.relationshipInRichText[0].type).toEqual('paragraph')
|
expect(merge2.richTextLexical.root.children[0].type).toEqual('relationship')
|
||||||
|
expect(merge2.richTextLexical.root.children[0].value).toMatchObject(testPost)
|
||||||
|
expect(merge2.richTextLexical.root.children[1].type).toEqual('paragraph')
|
||||||
|
expect(merge2.richTextLexical.root.children[2].type).toEqual('paragraph')
|
||||||
|
expect(merge2.richTextLexical.root.children[3].type).toEqual('upload')
|
||||||
|
expect(merge2.richTextLexical.root.children[3].value).toMatchObject(media)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('— relationships - does not re-populate existing rich text relationships', async () => {
|
it('— relationships - does not re-populate existing rich text relationships', async () => {
|
||||||
const initialData: Partial<Page> = {
|
const initialData: Partial<Page> = {
|
||||||
title: 'Test Page',
|
title: 'Test Page',
|
||||||
relationshipInRichText: [
|
richTextSlate: [
|
||||||
{
|
{
|
||||||
type: 'paragraph',
|
type: 'paragraph',
|
||||||
text: 'Paragraph 1',
|
text: 'Paragraph 1',
|
||||||
@@ -532,7 +886,7 @@ describe('Collections - Live Preview', () => {
|
|||||||
fieldSchema: schemaJSON,
|
fieldSchema: schemaJSON,
|
||||||
incomingData: {
|
incomingData: {
|
||||||
...initialData,
|
...initialData,
|
||||||
relationshipInRichText: [
|
richTextSlate: [
|
||||||
{
|
{
|
||||||
type: 'paragraph',
|
type: 'paragraph',
|
||||||
text: 'Paragraph 1 (Updated)',
|
text: 'Paragraph 1 (Updated)',
|
||||||
@@ -552,9 +906,9 @@ describe('Collections - Live Preview', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(merge1._numberOfRequests).toEqual(0)
|
expect(merge1._numberOfRequests).toEqual(0)
|
||||||
expect(merge1.relationshipInRichText).toHaveLength(2)
|
expect(merge1.richTextSlate).toHaveLength(2)
|
||||||
expect(merge1.relationshipInRichText[0].text).toEqual('Paragraph 1 (Updated)')
|
expect(merge1.richTextSlate[0].text).toEqual('Paragraph 1 (Updated)')
|
||||||
expect(merge1.relationshipInRichText[1].reference.value).toMatchObject(testPost)
|
expect(merge1.richTextSlate[1].reference.value).toMatchObject(testPost)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('— relationships - populates within blocks', async () => {
|
it('— relationships - populates within blocks', async () => {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import React from 'react'
|
|||||||
import { PAYLOAD_SERVER_URL } from '@/app/_api/serverURL'
|
import { PAYLOAD_SERVER_URL } from '@/app/_api/serverURL'
|
||||||
import { Hero } from '@/app/_components/Hero'
|
import { Hero } from '@/app/_components/Hero'
|
||||||
import { Blocks } from '@/app/_components/Blocks'
|
import { Blocks } from '@/app/_components/Blocks'
|
||||||
import { RelationshipsBlock } from '@/app/_blocks/Relationships'
|
|
||||||
|
|
||||||
export const PageClient: React.FC<{
|
export const PageClient: React.FC<{
|
||||||
page: PageType
|
page: PageType
|
||||||
|
|||||||
@@ -22,9 +22,15 @@ export const RelationshipsBlock: React.FC<RelationshipsBlockProps> = (props) =>
|
|||||||
This block is for testing purposes only. It renders every possible type of relationship.
|
This block is for testing purposes only. It renders every possible type of relationship.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>Rich Text:</b>
|
<b>Rich Text — Slate:</b>
|
||||||
</p>
|
</p>
|
||||||
{data?.relationshipInRichText && <RichText content={data.relationshipInRichText} />}
|
{data?.richTextSlate && <RichText content={data.richTextSlate} renderUploadFilenameOnly />}
|
||||||
|
<p>
|
||||||
|
<b>Rich Text — Lexical:</b>
|
||||||
|
</p>
|
||||||
|
{data?.richTextLexical && (
|
||||||
|
<RichText serializer="lexical" content={data.richTextLexical} renderUploadFilenameOnly />
|
||||||
|
)}
|
||||||
<p>
|
<p>
|
||||||
<b>Upload:</b>
|
<b>Upload:</b>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import serialize from './serialize'
|
import serializeSlate from './serializeSlate'
|
||||||
|
import serializeLexical from './serializeLexical'
|
||||||
|
|
||||||
import classes from './index.module.scss'
|
import classes from './index.module.scss'
|
||||||
|
|
||||||
const RichText: React.FC<{ className?: string; content: any }> = ({ className, content }) => {
|
const RichText: React.FC<{
|
||||||
|
className?: string
|
||||||
|
content: any
|
||||||
|
renderUploadFilenameOnly?: boolean
|
||||||
|
serializer?: 'lexical' | 'slate'
|
||||||
|
}> = ({ className, content, renderUploadFilenameOnly, serializer = 'slate' }) => {
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[classes.richText, className].filter(Boolean).join(' ')}>
|
<div className={[classes.richText, className].filter(Boolean).join(' ')}>
|
||||||
{serialize(content)}
|
{serializer === 'slate'
|
||||||
|
? serializeSlate(content, renderUploadFilenameOnly)
|
||||||
|
: serializeLexical(content, renderUploadFilenameOnly)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import type { SerializedEditorState } from 'lexical'
|
||||||
|
import { CMSLink } from '../Link'
|
||||||
|
import { Media } from '../Media'
|
||||||
|
|
||||||
|
const serializer = (
|
||||||
|
content?: SerializedEditorState['root']['children'],
|
||||||
|
renderUploadFilenameOnly?: boolean,
|
||||||
|
): React.ReactNode | React.ReactNode[] =>
|
||||||
|
content?.map((node, i) => {
|
||||||
|
switch (node.type) {
|
||||||
|
case 'h1':
|
||||||
|
return <h1 key={i}>{serializeLexical(node?.children, renderUploadFilenameOnly)}</h1>
|
||||||
|
|
||||||
|
case 'h2':
|
||||||
|
return <h2 key={i}>{serializeLexical(node?.children, renderUploadFilenameOnly)}</h2>
|
||||||
|
|
||||||
|
case 'h3':
|
||||||
|
return <h3 key={i}>{serializeLexical(node?.children, renderUploadFilenameOnly)}</h3>
|
||||||
|
|
||||||
|
case 'h4':
|
||||||
|
return <h4 key={i}>{serializeLexical(node?.children, renderUploadFilenameOnly)}</h4>
|
||||||
|
|
||||||
|
case 'h5':
|
||||||
|
return <h5 key={i}>{serializeLexical(node?.children, renderUploadFilenameOnly)}</h5>
|
||||||
|
|
||||||
|
case 'h6':
|
||||||
|
return <h6 key={i}>{serializeLexical(node?.children, renderUploadFilenameOnly)}</h6>
|
||||||
|
|
||||||
|
case 'quote':
|
||||||
|
return (
|
||||||
|
<blockquote key={i}>
|
||||||
|
{serializeLexical(node?.children, renderUploadFilenameOnly)}
|
||||||
|
</blockquote>
|
||||||
|
)
|
||||||
|
|
||||||
|
case 'ul':
|
||||||
|
return <ul key={i}>{serializeLexical(node?.children, renderUploadFilenameOnly)}</ul>
|
||||||
|
|
||||||
|
case 'ol':
|
||||||
|
return <ol key={i}>{serializeLexical(node.children, renderUploadFilenameOnly)}</ol>
|
||||||
|
|
||||||
|
case 'li':
|
||||||
|
return <li key={i}>{serializeLexical(node.children, renderUploadFilenameOnly)}</li>
|
||||||
|
|
||||||
|
case 'relationship':
|
||||||
|
return (
|
||||||
|
<span key={i}>
|
||||||
|
{node.value && typeof node.value === 'object'
|
||||||
|
? node.value.title || node.value.id
|
||||||
|
: node.value}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
|
||||||
|
case 'link':
|
||||||
|
return (
|
||||||
|
<CMSLink
|
||||||
|
key={i}
|
||||||
|
type={node.linkType === 'internal' ? 'reference' : 'custom'}
|
||||||
|
url={node.url}
|
||||||
|
reference={node.doc as any}
|
||||||
|
newTab={Boolean(node?.newTab)}
|
||||||
|
>
|
||||||
|
{serializer(node?.children, renderUploadFilenameOnly)}
|
||||||
|
</CMSLink>
|
||||||
|
)
|
||||||
|
|
||||||
|
case 'upload':
|
||||||
|
if (renderUploadFilenameOnly) {
|
||||||
|
return <span key={i}>{node.value.filename}</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Media key={i} resource={node?.value} />
|
||||||
|
|
||||||
|
case 'paragraph':
|
||||||
|
return <p key={i}>{serializer(node?.children, renderUploadFilenameOnly)}</p>
|
||||||
|
|
||||||
|
case 'text':
|
||||||
|
return <span key={i}>{node.text}</span>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const serializeLexical = (
|
||||||
|
content?: SerializedEditorState,
|
||||||
|
renderUploadFilenameOnly?: boolean,
|
||||||
|
): React.ReactNode | React.ReactNode[] => {
|
||||||
|
return serializer(content?.root?.children, renderUploadFilenameOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default serializeLexical
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import escapeHTML from 'escape-html'
|
import escapeHTML from 'escape-html'
|
||||||
import Link from 'next/link'
|
|
||||||
import { Text } from 'slate'
|
import { Text } from 'slate'
|
||||||
import { CMSLink } from '../Link'
|
import { CMSLink } from '../Link'
|
||||||
|
import { Media } from '../Media'
|
||||||
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
type Children = Leaf[]
|
type Children = Leaf[]
|
||||||
@@ -15,7 +15,10 @@ type Leaf = {
|
|||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
const serialize = (children?: Children): React.ReactNode[] =>
|
const serializeSlate = (
|
||||||
|
children?: Children,
|
||||||
|
renderUploadFilenameOnly?: boolean,
|
||||||
|
): React.ReactNode[] =>
|
||||||
children?.map((node, i) => {
|
children?.map((node, i) => {
|
||||||
if (Text.isText(node)) {
|
if (Text.isText(node)) {
|
||||||
let text = <span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
|
let text = <span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
|
||||||
@@ -57,25 +60,39 @@ const serialize = (children?: Children): React.ReactNode[] =>
|
|||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'h1':
|
case 'h1':
|
||||||
return <h1 key={i}>{serialize(node?.children)}</h1>
|
return <h1 key={i}>{serializeSlate(node?.children, renderUploadFilenameOnly)}</h1>
|
||||||
|
|
||||||
case 'h2':
|
case 'h2':
|
||||||
return <h2 key={i}>{serialize(node?.children)}</h2>
|
return <h2 key={i}>{serializeSlate(node?.children, renderUploadFilenameOnly)}</h2>
|
||||||
|
|
||||||
case 'h3':
|
case 'h3':
|
||||||
return <h3 key={i}>{serialize(node?.children)}</h3>
|
return <h3 key={i}>{serializeSlate(node?.children, renderUploadFilenameOnly)}</h3>
|
||||||
|
|
||||||
case 'h4':
|
case 'h4':
|
||||||
return <h4 key={i}>{serialize(node?.children)}</h4>
|
return <h4 key={i}>{serializeSlate(node?.children, renderUploadFilenameOnly)}</h4>
|
||||||
|
|
||||||
case 'h5':
|
case 'h5':
|
||||||
return <h5 key={i}>{serialize(node?.children)}</h5>
|
return <h5 key={i}>{serializeSlate(node?.children, renderUploadFilenameOnly)}</h5>
|
||||||
|
|
||||||
case 'h6':
|
case 'h6':
|
||||||
return <h6 key={i}>{serialize(node?.children)}</h6>
|
return <h6 key={i}>{serializeSlate(node?.children, renderUploadFilenameOnly)}</h6>
|
||||||
|
|
||||||
case 'quote':
|
case 'quote':
|
||||||
return <blockquote key={i}>{serialize(node?.children)}</blockquote>
|
return (
|
||||||
|
<blockquote key={i}>
|
||||||
|
{serializeSlate(node?.children, renderUploadFilenameOnly)}
|
||||||
|
</blockquote>
|
||||||
|
)
|
||||||
|
|
||||||
case 'ul':
|
case 'ul':
|
||||||
return <ul key={i}>{serialize(node?.children)}</ul>
|
return <ul key={i}>{serializeSlate(node?.children, renderUploadFilenameOnly)}</ul>
|
||||||
|
|
||||||
case 'ol':
|
case 'ol':
|
||||||
return <ol key={i}>{serialize(node.children)}</ol>
|
return <ol key={i}>{serializeSlate(node.children, renderUploadFilenameOnly)}</ol>
|
||||||
|
|
||||||
case 'li':
|
case 'li':
|
||||||
return <li key={i}>{serialize(node.children)}</li>
|
return <li key={i}>{serializeSlate(node.children, renderUploadFilenameOnly)}</li>
|
||||||
|
|
||||||
case 'relationship':
|
case 'relationship':
|
||||||
return (
|
return (
|
||||||
<span key={i}>
|
<span key={i}>
|
||||||
@@ -84,6 +101,7 @@ const serialize = (children?: Children): React.ReactNode[] =>
|
|||||||
: node.value}
|
: node.value}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
||||||
case 'link':
|
case 'link':
|
||||||
return (
|
return (
|
||||||
<CMSLink
|
<CMSLink
|
||||||
@@ -93,13 +111,20 @@ const serialize = (children?: Children): React.ReactNode[] =>
|
|||||||
reference={node.doc as any}
|
reference={node.doc as any}
|
||||||
newTab={Boolean(node?.newTab)}
|
newTab={Boolean(node?.newTab)}
|
||||||
>
|
>
|
||||||
{serialize(node?.children)}
|
{serializeSlate(node?.children, renderUploadFilenameOnly)}
|
||||||
</CMSLink>
|
</CMSLink>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case 'upload':
|
||||||
|
if (renderUploadFilenameOnly) {
|
||||||
|
return <span key={i}>{node.value.filename}</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Media key={i} resource={node?.value} />
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return <p key={i}>{serialize(node?.children)}</p>
|
return <p key={i}>{serializeSlate(node?.children, renderUploadFilenameOnly)}</p>
|
||||||
}
|
}
|
||||||
}) || []
|
}) || []
|
||||||
|
|
||||||
export default serialize
|
export default serializeSlate
|
||||||
@@ -11,6 +11,7 @@ export interface Config {
|
|||||||
users: User
|
users: User
|
||||||
pages: Page
|
pages: Page
|
||||||
posts: Post
|
posts: Post
|
||||||
|
tenants: Tenant
|
||||||
categories: Category
|
categories: Category
|
||||||
media: Media
|
media: Media
|
||||||
'payload-preferences': PayloadPreference
|
'payload-preferences': PayloadPreference
|
||||||
@@ -37,6 +38,7 @@ export interface User {
|
|||||||
export interface Page {
|
export interface Page {
|
||||||
id: string
|
id: string
|
||||||
slug: string
|
slug: string
|
||||||
|
tenant?: (string | null) | Tenant
|
||||||
title: string
|
title: string
|
||||||
hero: {
|
hero: {
|
||||||
type: 'none' | 'highImpact' | 'lowImpact'
|
type: 'none' | 'highImpact' | 'lowImpact'
|
||||||
@@ -152,16 +154,6 @@ export interface Page {
|
|||||||
}
|
}
|
||||||
)[]
|
)[]
|
||||||
| null
|
| null
|
||||||
meta?: {
|
|
||||||
title?: string | null
|
|
||||||
description?: string | null
|
|
||||||
image?: string | Media | null
|
|
||||||
}
|
|
||||||
relationshipInRichText?:
|
|
||||||
| {
|
|
||||||
[k: string]: unknown
|
|
||||||
}[]
|
|
||||||
| null
|
|
||||||
relationshipAsUpload?: string | Media | null
|
relationshipAsUpload?: string | Media | null
|
||||||
relationshipMonoHasOne?: (string | null) | Post
|
relationshipMonoHasOne?: (string | null) | Post
|
||||||
relationshipMonoHasMany?: (string | Post)[] | null
|
relationshipMonoHasMany?: (string | Post)[] | null
|
||||||
@@ -198,6 +190,41 @@ export interface Page {
|
|||||||
id?: string | null
|
id?: string | null
|
||||||
}[]
|
}[]
|
||||||
| null
|
| null
|
||||||
|
richTextSlate?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown
|
||||||
|
}[]
|
||||||
|
| null
|
||||||
|
richTextLexical?: {
|
||||||
|
root: {
|
||||||
|
children: {
|
||||||
|
type: string
|
||||||
|
version: number
|
||||||
|
[k: string]: unknown
|
||||||
|
}[]
|
||||||
|
direction: ('ltr' | 'rtl') | null
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''
|
||||||
|
indent: number
|
||||||
|
type: string
|
||||||
|
version: number
|
||||||
|
}
|
||||||
|
[k: string]: unknown
|
||||||
|
} | null
|
||||||
|
tab: {
|
||||||
|
relationshipInTab?: (string | null) | Post
|
||||||
|
}
|
||||||
|
meta?: {
|
||||||
|
title?: string | null
|
||||||
|
description?: string | null
|
||||||
|
image?: string | Media | null
|
||||||
|
}
|
||||||
|
updatedAt: string
|
||||||
|
createdAt: string
|
||||||
|
}
|
||||||
|
export interface Tenant {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
clientURL: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
}
|
}
|
||||||
@@ -221,6 +248,7 @@ export interface Media {
|
|||||||
export interface Post {
|
export interface Post {
|
||||||
id: string
|
id: string
|
||||||
slug: string
|
slug: string
|
||||||
|
tenant?: (string | null) | Tenant
|
||||||
title: string
|
title: string
|
||||||
hero: {
|
hero: {
|
||||||
type: 'none' | 'highImpact' | 'lowImpact'
|
type: 'none' | 'highImpact' | 'lowImpact'
|
||||||
|
|||||||
@@ -154,11 +154,6 @@ export interface Page {
|
|||||||
}
|
}
|
||||||
)[]
|
)[]
|
||||||
| null
|
| null
|
||||||
relationshipInRichText?:
|
|
||||||
| {
|
|
||||||
[k: string]: unknown
|
|
||||||
}[]
|
|
||||||
| null
|
|
||||||
relationshipAsUpload?: string | Media | null
|
relationshipAsUpload?: string | Media | null
|
||||||
relationshipMonoHasOne?: (string | null) | Post
|
relationshipMonoHasOne?: (string | null) | Post
|
||||||
relationshipMonoHasMany?: (string | Post)[] | null
|
relationshipMonoHasMany?: (string | Post)[] | null
|
||||||
@@ -195,6 +190,26 @@ export interface Page {
|
|||||||
id?: string | null
|
id?: string | null
|
||||||
}[]
|
}[]
|
||||||
| null
|
| null
|
||||||
|
richTextSlate?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown
|
||||||
|
}[]
|
||||||
|
| null
|
||||||
|
richTextLexical?: {
|
||||||
|
root: {
|
||||||
|
children: {
|
||||||
|
type: string
|
||||||
|
version: number
|
||||||
|
[k: string]: unknown
|
||||||
|
}[]
|
||||||
|
direction: ('ltr' | 'rtl') | null
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''
|
||||||
|
indent: number
|
||||||
|
type: string
|
||||||
|
version: number
|
||||||
|
}
|
||||||
|
[k: string]: unknown
|
||||||
|
} | null
|
||||||
tab: {
|
tab: {
|
||||||
relationshipInTab?: (string | null) | Post
|
relationshipInTab?: (string | null) | Post
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const home: Omit<Page, 'createdAt' | 'id' | 'updatedAt'> = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
relationshipAsUpload: '{{MEDIA_ID}}',
|
relationshipAsUpload: '{{MEDIA_ID}}',
|
||||||
relationshipInRichText: [
|
richTextSlate: [
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -108,7 +108,65 @@ export const home: Omit<Page, 'createdAt' | 'id' | 'updatedAt'> = {
|
|||||||
id: '{{POST_1_ID}}',
|
id: '{{POST_1_ID}}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
relationTo: 'media',
|
||||||
|
type: 'upload',
|
||||||
|
value: {
|
||||||
|
id: '{{MEDIA_ID}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
richTextLexical: {
|
||||||
|
root: {
|
||||||
|
type: 'root',
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
version: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
format: '',
|
||||||
|
type: 'relationship',
|
||||||
|
version: 1,
|
||||||
|
relationTo: 'posts',
|
||||||
|
value: {
|
||||||
|
id: '{{POST_1_ID}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: [],
|
||||||
|
direction: null,
|
||||||
|
format: '',
|
||||||
|
indent: 0,
|
||||||
|
type: 'paragraph',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: '',
|
||||||
|
type: 'upload',
|
||||||
|
version: 1,
|
||||||
|
fields: null,
|
||||||
|
relationTo: 'media',
|
||||||
|
value: {
|
||||||
|
id: '{{MEDIA_ID}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
direction: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
relationshipMonoHasMany: ['{{POST_1_ID}}'],
|
relationshipMonoHasMany: ['{{POST_1_ID}}'],
|
||||||
relationshipMonoHasOne: '{{POST_1_ID}}',
|
relationshipMonoHasOne: '{{POST_1_ID}}',
|
||||||
relationshipPolyHasMany: [{ relationTo: 'posts', value: '{{POST_1_ID}}' }],
|
relationshipPolyHasMany: [{ relationTo: 'posts', value: '{{POST_1_ID}}' }],
|
||||||
|
|||||||
Reference in New Issue
Block a user