fix(richtext-lexical): ensure hooks from sub-fields receive document data and doc props, and not node data and doc props (#9406)

This also adds a new `previousNode` prop to afterChange node hooks, and
makes sure that sub-fields receive the correct previousSiblingDoc.
This commit is contained in:
Alessio Gravili
2024-12-04 06:47:05 -07:00
committed by GitHub
parent f5aad49ba7
commit 50823be9e5
2 changed files with 71 additions and 29 deletions

View File

@@ -176,6 +176,7 @@ export type AfterChangeNodeHookArgs<T extends SerializedLexicalNode> = {
operation: 'create' | 'delete' | 'read' | 'update'
/** The value of the node before any changes. Not available in afterRead hooks */
originalNode: T
previousNode: T
}
export type BeforeValidateNodeHookArgs<T extends SerializedLexicalNode> = {
/** A string relating to which operation the field type is currently executing within. Useful within beforeValidate, beforeChange, and afterChange hooks to differentiate between create and update operations. */
@@ -199,6 +200,8 @@ export type BeforeChangeNodeHookArgs<T extends SerializedLexicalNode> = {
* The original node with locales (not modified by any hooks).
*/
originalNodeWithLocales?: T
previousNode: T
skipValidation: boolean
}

View File

@@ -183,7 +183,19 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
hooks: {
afterChange: [
async (args) => {
const { collection, context: _context, global, operation, path, req, schemaPath } = args
const {
collection,
context: _context,
data,
global,
operation,
originalDoc,
path,
previousDoc,
previousValue,
req,
schemaPath,
} = args
let { value } = args
if (finalSanitizedEditorConfig?.features?.hooks?.afterChange?.length) {
for (const hook of finalSanitizedEditorConfig.features.hooks.afterChange) {
@@ -203,6 +215,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
[key: string]: SerializedLexicalNode
} = {}
const previousNodeIDMap: {
[key: string]: SerializedLexicalNode
} = {}
/**
* Get the originalNodeIDMap from the beforeValidate hook, which is always run before this hook.
*/
@@ -219,6 +235,11 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
nodes: (value as SerializedEditorState)?.root?.children ?? [],
})
recurseNodeTree({
nodeIDMap: previousNodeIDMap,
nodes: (previousValue as SerializedEditorState)?.root?.children ?? [],
})
// eslint-disable-next-line prefer-const
for (let [id, node] of Object.entries(nodeIDMap)) {
const afterChangeHooks = finalSanitizedEditorConfig.features.nodeHooks?.afterChange
@@ -243,6 +264,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
originalNode: originalNodeIDMap[id],
parentRichTextFieldPath: path,
parentRichTextFieldSchemaPath: schemaPath,
previousNode: previousNodeIDMap[id],
req,
})
}
@@ -254,25 +276,27 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
if (subFieldFn && subFieldDataFn) {
const subFields = subFieldFn({ node, req })
const data = subFieldDataFn({ node, req }) ?? {}
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
const nodePreviousSiblingDoc =
subFieldDataFn({ node: previousNodeIDMap[id], req }) ?? {}
if (subFields?.length) {
await afterChangeTraverseFields({
collection,
context,
data: originalData,
doc: data,
data: data ?? {},
doc: originalDoc,
fields: subFields,
global,
operation,
path,
previousDoc: data,
previousSiblingDoc: { ...data },
previousDoc,
previousSiblingDoc: { ...nodePreviousSiblingDoc },
req,
schemaPath,
siblingData: originalData || {},
siblingDoc: { ...data },
siblingData: nodeSiblingData || {},
siblingDoc: { ...nodeSiblingDoc },
})
}
}
@@ -297,6 +321,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
flattenLocales,
global,
locale,
originalDoc,
overrideAccess,
path,
populate,
@@ -363,7 +388,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
if (subFieldFn && subFieldDataFn) {
const subFields = subFieldFn({ node, req })
const data = subFieldDataFn({ node, req }) ?? {}
const nodeSliblingData = subFieldDataFn({ node, req }) ?? {}
if (subFields?.length) {
afterReadTraverseFields({
@@ -371,7 +396,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
context,
currentDepth: currentDepth!,
depth: depth!,
doc: data,
doc: originalDoc,
draft: draft!,
fallbackLocale: fallbackLocale!,
fieldPromises: fieldPromises!,
@@ -387,7 +412,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
req,
schemaPath,
showHiddenFields: showHiddenFields!,
siblingDoc: data,
siblingDoc: nodeSliblingData,
triggerAccessControl,
triggerHooks,
})
@@ -403,12 +428,16 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
const {
collection,
context: _context,
data,
docWithLocales,
errors,
field,
global,
mergeLocaleActions,
operation,
originalDoc,
path,
previousValue,
req,
schemaPath,
siblingData,
@@ -447,7 +476,9 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
if (!originalNodeIDMap || !Object.keys(originalNodeIDMap).length || !value) {
return value
}
const previousNodeIDMap: {
[key: string]: SerializedLexicalNode
} = {}
const originalNodeWithLocalesIDMap: {
[key: string]: SerializedLexicalNode
} = {}
@@ -457,6 +488,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
nodes: (value as SerializedEditorState)?.root?.children ?? [],
})
recurseNodeTree({
nodeIDMap: previousNodeIDMap,
nodes: (previousValue as SerializedEditorState)?.root?.children ?? [],
})
if (field.name && siblingDocWithLocales?.[field.name]) {
recurseNodeTree({
nodeIDMap: originalNodeWithLocalesIDMap,
@@ -493,6 +528,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
originalNodeWithLocales: originalNodeWithLocalesIDMap[id],
parentRichTextFieldPath: path,
parentRichTextFieldSchemaPath: schemaPath,
previousNode: previousNodeIDMap[id],
req,
skipValidation: skipValidation!,
})
@@ -506,22 +542,23 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
if (subFieldFn && subFieldDataFn) {
const subFields = subFieldFn({ node, req })
const data = subFieldDataFn({ node, req }) ?? {}
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
const originalDataWithLocales =
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
const nodeSiblingDocWithLocales =
subFieldDataFn({
node: originalNodeWithLocalesIDMap[id],
req,
}) ?? {}
const nodePreviousSiblingDoc =
subFieldDataFn({ node: previousNodeIDMap[id], req }) ?? {}
if (subFields?.length) {
await beforeChangeTraverseFields({
id,
collection,
context,
data,
doc: originalData,
docWithLocales: originalDataWithLocales ?? {},
data: data ?? {},
doc: originalDoc ?? {},
docWithLocales: docWithLocales ?? {},
errors: errors!,
fields: subFields,
global,
@@ -530,9 +567,9 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
path,
req,
schemaPath,
siblingData: data,
siblingDoc: originalData,
siblingDocWithLocales: originalDataWithLocales ?? {},
siblingData: nodeSiblingData,
siblingDoc: nodePreviousSiblingDoc,
siblingDocWithLocales: nodeSiblingDocWithLocales ?? {},
skipValidation,
})
}
@@ -553,11 +590,11 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
[key: string]: SerializedLexicalNode
} = {}
const previousValue = siblingData[field.name!]
const previousOriginalValue = siblingData[field.name!]
recurseNodeTree({
nodeIDMap: newOriginalNodeIDMap,
nodes: (previousValue as SerializedEditorState)?.root?.children ?? [],
nodes: (previousOriginalValue as SerializedEditorState)?.root?.children ?? [],
})
if (!context.internal) {
@@ -579,8 +616,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
const {
collection,
context,
data,
global,
operation,
originalDoc,
overrideAccess,
path,
previousValue,
@@ -700,8 +739,8 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
if (subFieldFn && subFieldDataFn) {
const subFields = subFieldFn({ node, req })
const data = subFieldDataFn({ node, req }) ?? {}
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
const nodeSiblingData = subFieldDataFn({ node, req }) ?? {}
const nodeSiblingDoc = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
if (subFields?.length) {
await beforeValidateTraverseFields({
@@ -709,7 +748,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
collection,
context,
data,
doc: originalData,
doc: originalDoc,
fields: subFields,
global,
operation,
@@ -717,8 +756,8 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
path,
req,
schemaPath,
siblingData: data,
siblingDoc: originalData,
siblingData: nodeSiblingData,
siblingDoc: nodeSiblingDoc,
})
}
}