fix(live-preview): compounds merge results (#4306)
This commit is contained in:
@@ -6,6 +6,10 @@ import { mergeData } from '.'
|
||||
// Send this cached value to `mergeData`, instead of `eventData.fieldSchemaJSON` directly
|
||||
let payloadLivePreviewFieldSchema = undefined // TODO: type this from `fieldSchemaToJSON` return type
|
||||
|
||||
// Each time the data is merged, cache the result as a `previousData` variable
|
||||
// This will ensure changes compound overtop of each other
|
||||
let payloadLivePreviewPreviousData = undefined
|
||||
|
||||
export const handleMessage = async <T>(args: {
|
||||
apiRoute?: string
|
||||
depth?: number
|
||||
@@ -37,10 +41,12 @@ export const handleMessage = async <T>(args: {
|
||||
depth,
|
||||
fieldSchema: payloadLivePreviewFieldSchema,
|
||||
incomingData: eventData.data,
|
||||
initialData,
|
||||
initialData: payloadLivePreviewPreviousData || initialData,
|
||||
serverURL,
|
||||
})
|
||||
|
||||
payloadLivePreviewPreviousData = mergedData
|
||||
|
||||
return mergedData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,50 +11,68 @@ export const traverseRichText = ({
|
||||
apiRoute: string
|
||||
depth: number
|
||||
incomingData: any
|
||||
populationPromises: Promise<void>[]
|
||||
populationPromises: Promise<any>[]
|
||||
result: any
|
||||
serverURL: string
|
||||
}): any => {
|
||||
if (Array.isArray(incomingData)) {
|
||||
result = incomingData.map((incomingRow) =>
|
||||
traverseRichText({
|
||||
if (!result) {
|
||||
result = []
|
||||
}
|
||||
|
||||
result = incomingData.map((item, index) => {
|
||||
if (!result[index]) {
|
||||
result[index] = item
|
||||
}
|
||||
|
||||
return traverseRichText({
|
||||
apiRoute,
|
||||
depth,
|
||||
incomingData: incomingRow,
|
||||
incomingData: item,
|
||||
populationPromises,
|
||||
result,
|
||||
result: result[index],
|
||||
serverURL,
|
||||
}),
|
||||
)
|
||||
} else if (typeof incomingData === 'object' && incomingData !== null) {
|
||||
result = incomingData
|
||||
})
|
||||
})
|
||||
} else if (incomingData && typeof incomingData === 'object') {
|
||||
if (!result) {
|
||||
result = {}
|
||||
}
|
||||
|
||||
if ('relationTo' in incomingData && 'value' in incomingData && incomingData.value) {
|
||||
Object.keys(incomingData).forEach((key) => {
|
||||
if (!result[key]) {
|
||||
result[key] = incomingData[key]
|
||||
}
|
||||
|
||||
const isRelationship = key === 'value' && 'relationTo' in incomingData
|
||||
|
||||
if (isRelationship) {
|
||||
const needsPopulation = !result.value || typeof result.value !== 'object'
|
||||
|
||||
if (needsPopulation) {
|
||||
populationPromises.push(
|
||||
promise({
|
||||
id: typeof incomingData.value === 'object' ? incomingData.value.id : incomingData.value,
|
||||
id: incomingData[key],
|
||||
accessor: 'value',
|
||||
apiRoute,
|
||||
collection: String(incomingData.relationTo),
|
||||
collection: incomingData.relationTo,
|
||||
depth,
|
||||
ref: result,
|
||||
serverURL,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
result = {}
|
||||
|
||||
Object.keys(incomingData).forEach((key) => {
|
||||
result[key] = traverseRichText({
|
||||
apiRoute,
|
||||
depth,
|
||||
incomingData: incomingData[key],
|
||||
populationPromises,
|
||||
result,
|
||||
result: result[key],
|
||||
serverURL,
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
result = incomingData
|
||||
}
|
||||
|
||||
@@ -187,8 +187,7 @@ describe('Collections - Live Preview', () => {
|
||||
|
||||
expect(mergedDataWithoutUpload.hero.media).toBeFalsy()
|
||||
})
|
||||
|
||||
it('— relationships - populates all types', async () => {
|
||||
it('— relationships - populates monomorphic has one relationships', async () => {
|
||||
const initialData: Partial<Page> = {
|
||||
title: 'Test Page',
|
||||
}
|
||||
@@ -199,8 +198,71 @@ describe('Collections - Live Preview', () => {
|
||||
incomingData: {
|
||||
...initialData,
|
||||
relationshipMonoHasOne: testPost.id,
|
||||
},
|
||||
initialData,
|
||||
serverURL,
|
||||
returnNumberOfRequests: true,
|
||||
})
|
||||
|
||||
expect(merge1._numberOfRequests).toEqual(1)
|
||||
expect(merge1.relationshipMonoHasOne).toMatchObject(testPost)
|
||||
})
|
||||
|
||||
it('— relationships - populates monomorphic has many relationships', async () => {
|
||||
const initialData: Partial<Page> = {
|
||||
title: 'Test Page',
|
||||
}
|
||||
|
||||
const merge1 = await mergeData({
|
||||
depth: 1,
|
||||
fieldSchema: schemaJSON,
|
||||
incomingData: {
|
||||
...initialData,
|
||||
relationshipMonoHasMany: [testPost.id],
|
||||
},
|
||||
initialData,
|
||||
serverURL,
|
||||
returnNumberOfRequests: true,
|
||||
})
|
||||
|
||||
expect(merge1._numberOfRequests).toEqual(1)
|
||||
expect(merge1.relationshipMonoHasMany).toMatchObject([testPost])
|
||||
})
|
||||
|
||||
it('— relationships - populates polymorphic has one relationships', async () => {
|
||||
const initialData: Partial<Page> = {
|
||||
title: 'Test Page',
|
||||
}
|
||||
|
||||
const merge1 = await mergeData({
|
||||
depth: 1,
|
||||
fieldSchema: schemaJSON,
|
||||
incomingData: {
|
||||
...initialData,
|
||||
relationshipPolyHasOne: { value: testPost.id, relationTo: postsSlug },
|
||||
},
|
||||
initialData,
|
||||
serverURL,
|
||||
returnNumberOfRequests: true,
|
||||
})
|
||||
|
||||
expect(merge1._numberOfRequests).toEqual(1)
|
||||
expect(merge1.relationshipPolyHasOne).toMatchObject({
|
||||
value: testPost,
|
||||
relationTo: postsSlug,
|
||||
})
|
||||
})
|
||||
|
||||
it('— relationships - populates polymorphic has many relationships', async () => {
|
||||
const initialData: Partial<Page> = {
|
||||
title: 'Test Page',
|
||||
}
|
||||
|
||||
const merge1 = await mergeData({
|
||||
depth: 1,
|
||||
fieldSchema: schemaJSON,
|
||||
incomingData: {
|
||||
...initialData,
|
||||
relationshipPolyHasMany: [{ value: testPost.id, relationTo: postsSlug }],
|
||||
},
|
||||
initialData,
|
||||
@@ -208,19 +270,12 @@ describe('Collections - Live Preview', () => {
|
||||
returnNumberOfRequests: true,
|
||||
})
|
||||
|
||||
expect(merge1._numberOfRequests).toEqual(4)
|
||||
expect(merge1.relationshipMonoHasOne).toMatchObject(testPost)
|
||||
expect(merge1.relationshipMonoHasMany).toMatchObject([testPost])
|
||||
|
||||
expect(merge1.relationshipPolyHasOne).toMatchObject({
|
||||
value: testPost,
|
||||
relationTo: postsSlug,
|
||||
})
|
||||
|
||||
expect(merge1._numberOfRequests).toEqual(1)
|
||||
expect(merge1.relationshipPolyHasMany).toMatchObject([
|
||||
{ value: testPost, relationTo: postsSlug },
|
||||
])
|
||||
})
|
||||
|
||||
it('— relationships - can clear relationships', async () => {
|
||||
const initialData: Partial<Page> = {
|
||||
title: 'Test Page',
|
||||
@@ -421,6 +476,114 @@ describe('Collections - Live Preview', () => {
|
||||
expect(merge2.relationshipInRichText[0].type).toEqual('paragraph')
|
||||
})
|
||||
|
||||
it('— relationships - does not re-populate existing rich text relationships', async () => {
|
||||
const initialData: Partial<Page> = {
|
||||
title: 'Test Page',
|
||||
relationshipInRichText: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Paragraph 1',
|
||||
},
|
||||
{
|
||||
type: 'reference',
|
||||
reference: {
|
||||
relationTo: 'posts',
|
||||
value: testPost,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// Add a relationship
|
||||
const merge1 = await mergeData({
|
||||
depth: 1,
|
||||
fieldSchema: schemaJSON,
|
||||
incomingData: {
|
||||
...initialData,
|
||||
relationshipInRichText: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Paragraph 1',
|
||||
},
|
||||
{
|
||||
type: 'reference',
|
||||
reference: {
|
||||
relationTo: 'posts',
|
||||
value: testPost.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
initialData,
|
||||
serverURL,
|
||||
returnNumberOfRequests: true,
|
||||
})
|
||||
|
||||
expect(merge1._numberOfRequests).toEqual(0)
|
||||
expect(merge1.relationshipInRichText).toHaveLength(2)
|
||||
expect(merge1.relationshipInRichText[1].reference.value).toMatchObject(testPost)
|
||||
})
|
||||
|
||||
it('— relationships - populates within blocks', async () => {
|
||||
const block1 = (shallow?: boolean): Extract<Page['layout'][0], { blockType: 'cta' }> => ({
|
||||
blockType: 'cta',
|
||||
id: '123',
|
||||
links: [
|
||||
{
|
||||
link: {
|
||||
label: 'Link 1',
|
||||
type: 'reference',
|
||||
reference: {
|
||||
relationTo: 'posts',
|
||||
value: shallow ? testPost?.id : testPost,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const block2: Extract<Page['layout'][0], { blockType: 'content' }> = {
|
||||
blockType: 'content',
|
||||
id: '456',
|
||||
columns: [
|
||||
{
|
||||
id: '789',
|
||||
richText: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Column 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const initialData: Partial<Page> = {
|
||||
title: 'Test Page',
|
||||
layout: [block1(), block2],
|
||||
}
|
||||
|
||||
// Add a new block before the populated one
|
||||
// Then check to see that the relationship is still populated
|
||||
const merge2 = await mergeData({
|
||||
depth: 1,
|
||||
fieldSchema: schemaJSON,
|
||||
incomingData: {
|
||||
...initialData,
|
||||
layout: [block2, block1(true)],
|
||||
},
|
||||
initialData,
|
||||
serverURL,
|
||||
returnNumberOfRequests: true,
|
||||
})
|
||||
|
||||
// Check that the relationship on the first has been removed
|
||||
// And that the relationship on the second has been populated
|
||||
expect(merge2.layout[0].links).toBeUndefined()
|
||||
expect(merge2.layout[1].links[0].link.reference.value).toMatchObject(testPost)
|
||||
expect(merge2._numberOfRequests).toEqual(1)
|
||||
})
|
||||
|
||||
it('— rich text - merges rich text', async () => {
|
||||
const initialData: Partial<Page> = {
|
||||
title: 'Test Page',
|
||||
@@ -485,66 +648,6 @@ describe('Collections - Live Preview', () => {
|
||||
expect(merge2.hero.richText[0].children[0].text).toEqual('Paragraph 1 (Updated)')
|
||||
})
|
||||
|
||||
it('— relationships - populates within blocks', async () => {
|
||||
const block1 = (shallow?: boolean): Extract<Page['layout'][0], { blockType: 'cta' }> => ({
|
||||
blockType: 'cta',
|
||||
id: '123',
|
||||
links: [
|
||||
{
|
||||
link: {
|
||||
label: 'Link 1',
|
||||
type: 'reference',
|
||||
reference: {
|
||||
relationTo: 'posts',
|
||||
value: shallow ? testPost?.id : testPost,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const block2: Extract<Page['layout'][0], { blockType: 'content' }> = {
|
||||
blockType: 'content',
|
||||
id: '456',
|
||||
columns: [
|
||||
{
|
||||
id: '789',
|
||||
richText: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Column 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const initialData: Partial<Page> = {
|
||||
title: 'Test Page',
|
||||
layout: [block1(), block2],
|
||||
}
|
||||
|
||||
// Add a new block before the populated one
|
||||
// Then check to see that the relationship is still populated
|
||||
const merge2 = await mergeData({
|
||||
depth: 1,
|
||||
fieldSchema: schemaJSON,
|
||||
incomingData: {
|
||||
...initialData,
|
||||
layout: [block2, block1(true)],
|
||||
},
|
||||
initialData,
|
||||
serverURL,
|
||||
returnNumberOfRequests: true,
|
||||
})
|
||||
|
||||
// Check that the relationship on the first has been removed
|
||||
// And that the relationship on the second has been populated
|
||||
expect(merge2.layout[0].links).toBeUndefined()
|
||||
expect(merge2.layout[1].links[0].link.reference.value).toMatchObject(testPost)
|
||||
expect(merge2._numberOfRequests).toEqual(1)
|
||||
})
|
||||
|
||||
it('— blocks - adds, reorders, and removes blocks', async () => {
|
||||
const block1ID = '123'
|
||||
const block2ID = '456'
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { Fragment } from 'react'
|
||||
import escapeHTML from 'escape-html'
|
||||
import Link from 'next/link'
|
||||
import { Text } from 'slate'
|
||||
import { CMSLink } from '../Link'
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
type Children = Leaf[]
|
||||
@@ -85,18 +86,15 @@ const serialize = (children?: Children): React.ReactNode[] =>
|
||||
)
|
||||
case 'link':
|
||||
return (
|
||||
<Link
|
||||
href={escapeHTML(node.url)}
|
||||
<CMSLink
|
||||
key={i}
|
||||
{...(node?.newTab
|
||||
? {
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
}
|
||||
: {})}
|
||||
type={node.linkType === 'internal' ? 'reference' : 'custom'}
|
||||
url={node.url}
|
||||
reference={node.doc as any}
|
||||
newTab={Boolean(node?.newTab)}
|
||||
>
|
||||
{serialize(node?.children)}
|
||||
</Link>
|
||||
</CMSLink>
|
||||
)
|
||||
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user