feat(richtext-lexical)!: link node: change doc data format to be consistent with relationship field (#4504)

BREAKING: An unpopulated, internal link node no longer saves the doc id under fields.doc.value.id. Now, it saves it under fields.doc.value.

Migration inside of payload is automatic. If you are reading from the link node inside of your frontend, though, you will have to adjust it.

The version property of the link and autoLink node has been changed from 1 to 2.
This commit is contained in:
Alessio Gravili
2023-12-13 22:57:08 +01:00
committed by GitHub
parent 9e7a8c7206
commit cc0ba89518
9 changed files with 49 additions and 29 deletions

View File

@@ -114,7 +114,9 @@ export const LinkFeature = (props: LinkFeatureProps): FeatureProvider => {
const rel: string = node.fields.newTab ? ' rel="noopener noreferrer"' : ''
const href: string =
node.fields.linkType === 'custom' ? node.fields.url : node.fields.doc?.value?.id
node.fields.linkType === 'custom'
? node.fields.url
: (node.fields.doc?.value as string)
return `<a href="${href}"${rel}>${childrenText}</a>`
},
@@ -141,8 +143,13 @@ export const LinkFeature = (props: LinkFeatureProps): FeatureProvider => {
const rel: string = node.fields.newTab ? ' rel="noopener noreferrer"' : ''
const href: string =
node.fields.linkType === 'custom' ? node.fields.url : node.fields.doc?.value?.id
let href: string = node.fields.url
if (node.fields.linkType === 'internal') {
href =
typeof node.fields.doc?.value === 'string'
? node.fields.doc?.value
: node.fields.doc?.value?.id
}
return `<a href="${href}"${rel}>${childrenText}</a>`
},

View File

@@ -28,6 +28,15 @@ export class AutoLinkNode extends LinkNode {
}
static importJSON(serializedNode: SerializedAutoLinkNode): AutoLinkNode {
if (
serializedNode.version === 1 &&
typeof serializedNode.fields?.doc?.value === 'object' &&
serializedNode.fields?.doc?.value?.id
) {
serializedNode.fields.doc.value = serializedNode.fields.doc.value.id
serializedNode.version = 2
}
const node = $createAutoLinkNode({ fields: serializedNode.fields })
node.setFormat(serializedNode.format)
@@ -40,7 +49,7 @@ export class AutoLinkNode extends LinkNode {
return {
...super.exportJSON(),
type: 'autolink',
version: 1,
version: 2,
}
}

View File

@@ -27,11 +27,13 @@ export type LinkFields = {
[key: string]: unknown
doc: {
relationTo: string
value: {
value:
| {
// Actual doc data, populated in afterRead hook
[key: string]: unknown
id: string
}
| string
} | null
linkType: 'custom' | 'internal'
newTab: boolean
@@ -88,6 +90,15 @@ export class LinkNode extends ElementNode {
}
static importJSON(serializedNode: SerializedLinkNode): LinkNode {
if (
serializedNode.version === 1 &&
typeof serializedNode.fields?.doc?.value === 'object' &&
serializedNode.fields?.doc?.value?.id
) {
serializedNode.fields.doc.value = serializedNode.fields.doc.value.id
serializedNode.version = 2
}
const node = $createLinkNode({
fields: serializedNode.fields,
})
@@ -131,7 +142,7 @@ export class LinkNode extends ElementNode {
...super.exportJSON(),
fields: this.getFields(),
type: this.getType(),
version: 1,
version: 2,
}
}

View File

@@ -125,7 +125,7 @@ export function LinkEditor({
// internal link
setLinkUrl(
`/admin/collections/${linkParent.getFields()?.doc?.relationTo}/${linkParent.getFields()
?.doc?.value?.id}`,
?.doc?.value}`,
)
const relatedField = config.collections.find(
@@ -323,11 +323,6 @@ export function LinkEditor({
handleModalSubmit={(fields: Fields, data: Data) => {
closeModal(drawerSlug)
if (data?.fields?.doc?.value) {
data.fields.doc.value = {
id: data.fields.doc.value,
}
}
const newLinkPayload: LinkPayload = data as LinkPayload
editor.dispatchCommand(TOGGLE_LINK_COMMAND, newLinkPayload)

View File

@@ -25,13 +25,16 @@ export const linkPopulationPromiseHOC = (
}) => {
const promises: Promise<void>[] = []
if (node?.fields?.doc?.value?.id && node?.fields?.doc?.relationTo) {
if (node?.fields?.doc?.value && node?.fields?.doc?.relationTo) {
const collection = req.payload.collections[node?.fields?.doc?.relationTo]
if (collection) {
promises.push(
populate({
id: node?.fields?.doc?.value?.id,
id:
typeof node?.fields?.doc?.value === 'object'
? node?.fields?.doc?.value?.id
: node?.fields?.doc?.value,
collection,
currentDepth,
data: node?.fields?.doc,

View File

@@ -17,9 +17,7 @@ export const LinkConverter: LexicalPluginNodeConverter = {
doc: (lexicalPluginNode as any).attributes?.doc
? {
relationTo: (lexicalPluginNode as any).attributes?.doc?.relationTo,
value: {
id: (lexicalPluginNode as any).attributes?.doc?.value,
},
value: (lexicalPluginNode as any).attributes?.doc?.value,
}
: undefined,
linkType: (lexicalPluginNode as any).attributes?.linkType || 'custom',
@@ -29,7 +27,7 @@ export const LinkConverter: LexicalPluginNodeConverter = {
format: (lexicalPluginNode as any).format || '',
indent: (lexicalPluginNode as any).indent || 0,
type: 'link',
version: 1,
version: 2,
} as const as SerializedLinkNode
},
nodeTypes: ['link'],

View File

@@ -23,7 +23,7 @@ export const SlateLinkConverter: SlateNodeConverter = {
format: '',
indent: 0,
type: 'link',
version: 1,
version: 2,
} as const as SerializedLinkNode
},
nodeTypes: ['link'],

View File

@@ -54,7 +54,7 @@ export function generateLexicalRichText() {
format: '',
indent: 0,
type: 'link',
version: 1,
version: 2,
fields: {
url: 'https://payloadcms.com',
newTab: true,
@@ -86,13 +86,11 @@ export function generateLexicalRichText() {
format: '',
indent: 0,
type: 'link',
version: 1,
version: 2,
fields: {
url: 'https://',
doc: {
value: {
id: '{{ARRAY_DOC_ID}}',
},
value: '{{ARRAY_DOC_ID}}',
relationTo: 'array-fields',
},
newTab: false,

View File

@@ -174,7 +174,6 @@ describe('Lexical', () => {
)
expect(richTextDoc?.lexicalCustomFields).not.toStrictEqual(seededDocument) // The whole seededDocument should not match, as richTextDoc should now contain populated documents not present in the seeded document
expect(richTextDoc?.lexicalCustomFields).toMatchObject(seededDocument) // subset of seededDocument should match
const lexical: SerializedEditorState = richTextDoc?.lexicalCustomFields