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:
@@ -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>`
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,11 +27,13 @@ export type LinkFields = {
|
||||
[key: string]: unknown
|
||||
doc: {
|
||||
relationTo: string
|
||||
value: {
|
||||
// Actual doc data, populated in afterRead hook
|
||||
[key: string]: unknown
|
||||
id: string
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -23,7 +23,7 @@ export const SlateLinkConverter: SlateNodeConverter = {
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'link',
|
||||
version: 1,
|
||||
version: 2,
|
||||
} as const as SerializedLinkNode
|
||||
},
|
||||
nodeTypes: ['link'],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user