chore(richtext-lexical): add strictNullChecks to the richtext-lexical package (#8295)
This PR addresses around 500 TypeScript errors by enabling strictNullChecks in the richtext-lexical package. In the process, several bugs were identified and fixed. In some cases, I applied non-null assertions where necessary, although there may be room for further type refinement in the future. The focus of this PR is to resolve the immediate issues without introducing additional technical debt, rather than aiming for perfect type definitions at this stage. --------- Co-authored-by: Alessio Gravili <alessio@gravili.de>
This commit is contained in:
@@ -443,7 +443,7 @@ export interface FieldBaseClient {
|
|||||||
* Must not be one of reserved field names: ['__v', 'salt', 'hash', 'file']
|
* Must not be one of reserved field names: ['__v', 'salt', 'hash', 'file']
|
||||||
* @link https://payloadcms.com/docs/fields/overview#field-names
|
* @link https://payloadcms.com/docs/fields/overview#field-names
|
||||||
*/
|
*/
|
||||||
name?: string
|
name: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
saveToJWT?: boolean | string
|
saveToJWT?: boolean | string
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ import localOperations from './collections/operations/local/index.js'
|
|||||||
import { consoleEmailAdapter } from './email/consoleEmailAdapter.js'
|
import { consoleEmailAdapter } from './email/consoleEmailAdapter.js'
|
||||||
import { fieldAffectsData } from './fields/config/types.js'
|
import { fieldAffectsData } from './fields/config/types.js'
|
||||||
import localGlobalOperations from './globals/operations/local/index.js'
|
import localGlobalOperations from './globals/operations/local/index.js'
|
||||||
import { checkDependencies } from './utilities/dependencies/dependencyChecker.js'
|
|
||||||
import flattenFields from './utilities/flattenTopLevelFields.js'
|
|
||||||
import { getLogger } from './utilities/logger.js'
|
import { getLogger } from './utilities/logger.js'
|
||||||
import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit.js'
|
import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit.js'
|
||||||
import { traverseFields } from './utilities/traverseFields.js'
|
import { traverseFields } from './utilities/traverseFields.js'
|
||||||
@@ -91,6 +89,9 @@ export interface GeneratedTypes {
|
|||||||
collectionsUntyped: {
|
collectionsUntyped: {
|
||||||
[slug: string]: JsonObject & TypeWithID
|
[slug: string]: JsonObject & TypeWithID
|
||||||
}
|
}
|
||||||
|
dbUntyped: {
|
||||||
|
defaultIDType: number | string
|
||||||
|
}
|
||||||
globalsUntyped: {
|
globalsUntyped: {
|
||||||
[slug: string]: JsonObject
|
[slug: string]: JsonObject
|
||||||
}
|
}
|
||||||
@@ -115,6 +116,13 @@ type StringKeyOf<T> = Extract<keyof T, string>
|
|||||||
|
|
||||||
// Define the types for slugs using the appropriate collections and globals
|
// Define the types for slugs using the appropriate collections and globals
|
||||||
export type CollectionSlug = StringKeyOf<TypedCollection>
|
export type CollectionSlug = StringKeyOf<TypedCollection>
|
||||||
|
|
||||||
|
type ResolveDbType<T> = 'db' extends keyof T
|
||||||
|
? T['db']
|
||||||
|
: // @ts-expect-error
|
||||||
|
T['dbUntyped']
|
||||||
|
|
||||||
|
export type DefaultDocumentIDType = ResolveDbType<GeneratedTypes>['defaultIDType']
|
||||||
export type GlobalSlug = StringKeyOf<TypedGlobal>
|
export type GlobalSlug = StringKeyOf<TypedGlobal>
|
||||||
|
|
||||||
// now for locale and user
|
// now for locale and user
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const DynamicFieldSelector: React.FC<
|
|||||||
<SelectField
|
<SelectField
|
||||||
{...props}
|
{...props}
|
||||||
field={{
|
field={{
|
||||||
|
name: props?.field?.name,
|
||||||
options,
|
options,
|
||||||
...(props.field || {}),
|
...(props.field || {}),
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const RichTextCell: React.FC<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const finalSanitizedEditorConfig = useMemo<SanitizedClientEditorConfig>(() => {
|
const finalSanitizedEditorConfig = useMemo<SanitizedClientEditorConfig>(() => {
|
||||||
const clientFeatures: GeneratedFeatureProviderComponent[] = richTextComponentMap.get(
|
const clientFeatures: GeneratedFeatureProviderComponent[] = richTextComponentMap?.get(
|
||||||
'features',
|
'features',
|
||||||
) as GeneratedFeatureProviderComponent[]
|
) as GeneratedFeatureProviderComponent[]
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const MarkdownTransformer: ElementTransformer = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lines = exportChildren(node).split('\n')
|
const lines = exportChildren(node).split('\n')
|
||||||
const output = []
|
const output: string[] = []
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
output.push('> ' + line)
|
output.push('> ' + line)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import React, { useCallback } from 'react'
|
|||||||
|
|
||||||
import type { LexicalRichTextFieldProps } from '../../../../types.js'
|
import type { LexicalRichTextFieldProps } from '../../../../types.js'
|
||||||
import type { BlockFields } from '../../server/nodes/BlocksNode.js'
|
import type { BlockFields } from '../../server/nodes/BlocksNode.js'
|
||||||
import type { BlockNode } from '../nodes/BlocksNode.js'
|
|
||||||
|
|
||||||
|
import { $isBlockNode } from '../nodes/BlocksNode.js'
|
||||||
import { FormSavePlugin } from './FormSavePlugin.js'
|
import { FormSavePlugin } from './FormSavePlugin.js'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -122,8 +122,8 @@ export const BlockContent: React.FC<Props> = (props) => {
|
|||||||
// ensure that the nested editor has finished its update cycle before we update the block node.
|
// ensure that the nested editor has finished its update cycle before we update the block node.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
const node: BlockNode = $getNodeByKey(nodeKey)
|
const node = $getNodeByKey(nodeKey)
|
||||||
if (node) {
|
if (node && $isBlockNode(node)) {
|
||||||
formData = newFormData
|
formData = newFormData
|
||||||
node.setFields(newFormData)
|
node.setFields(newFormData)
|
||||||
}
|
}
|
||||||
@@ -176,7 +176,7 @@ export const BlockContent: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const removeBlock = useCallback(() => {
|
const removeBlock = useCallback(() => {
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
$getNodeByKey(nodeKey).remove()
|
$getNodeByKey(nodeKey)?.remove()
|
||||||
})
|
})
|
||||||
}, [editor, nodeKey])
|
}, [editor, nodeKey])
|
||||||
|
|
||||||
@@ -198,11 +198,11 @@ export const BlockContent: React.FC<Props> = (props) => {
|
|||||||
className={`${baseClass}__block-pill ${baseClass}__block-pill-${formData?.blockType}`}
|
className={`${baseClass}__block-pill ${baseClass}__block-pill-${formData?.blockType}`}
|
||||||
pillStyle="white"
|
pillStyle="white"
|
||||||
>
|
>
|
||||||
{typeof clientBlock?.labels.singular === 'string'
|
{typeof clientBlock?.labels?.singular === 'string'
|
||||||
? getTranslation(clientBlock?.labels.singular, i18n)
|
? getTranslation(clientBlock?.labels.singular, i18n)
|
||||||
: clientBlock.slug}
|
: clientBlock.slug}
|
||||||
</Pill>
|
</Pill>
|
||||||
<SectionTitle path="blockName" readOnly={field?.admin?.readOnly} />
|
<SectionTitle path="blockName" readOnly={field?.admin?.readOnly || false} />
|
||||||
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
|
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
|
||||||
</div>
|
</div>
|
||||||
{editor.isEditable() && (
|
{editor.isEditable() && (
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { FormProps } from '@payloadcms/ui'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
Form,
|
Form,
|
||||||
@@ -33,7 +31,7 @@ import './index.scss'
|
|||||||
type Props = {
|
type Props = {
|
||||||
readonly children?: React.ReactNode
|
readonly children?: React.ReactNode
|
||||||
readonly formData: BlockFields
|
readonly formData: BlockFields
|
||||||
readonly nodeKey?: string
|
readonly nodeKey: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BlockComponent: React.FC<Props> = (props) => {
|
export const BlockComponent: React.FC<Props> = (props) => {
|
||||||
@@ -52,13 +50,16 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
|||||||
const schemaFieldsPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_blocks.lexical_blocks.${formData.blockType}`
|
const schemaFieldsPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_blocks.lexical_blocks.${formData.blockType}`
|
||||||
|
|
||||||
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_blocks`
|
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_blocks`
|
||||||
const blocksField: BlocksFieldClient = richTextComponentMap.get(componentMapRenderedBlockPath)[0]
|
const blocksField: BlocksFieldClient = richTextComponentMap?.get(componentMapRenderedBlockPath)[0]
|
||||||
|
|
||||||
const clientBlock = blocksField.blocks.find((block) => block.slug === formData.blockType)
|
const clientBlock = blocksField.blocks.find((block) => block.slug === formData.blockType)
|
||||||
|
|
||||||
// Field Schema
|
// Field Schema
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const awaitInitialState = async () => {
|
const awaitInitialState = async () => {
|
||||||
|
if (!id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const { state } = await getFormState({
|
const { state } = await getFormState({
|
||||||
apiRoute: config.routes.api,
|
apiRoute: config.routes.api,
|
||||||
body: {
|
body: {
|
||||||
@@ -87,8 +88,11 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
}, [config.routes.api, config.serverURL, schemaFieldsPath, id]) // DO NOT ADD FORMDATA HERE! Adding formData will kick you out of sub block editors while writing.
|
}, [config.routes.api, config.serverURL, schemaFieldsPath, id]) // DO NOT ADD FORMDATA HERE! Adding formData will kick you out of sub block editors while writing.
|
||||||
|
|
||||||
const onChange: FormProps['onChange'][0] = useCallback(
|
const onChange = useCallback(
|
||||||
async ({ formState: prevFormState }) => {
|
async ({ formState: prevFormState }) => {
|
||||||
|
if (!id) {
|
||||||
|
throw new Error('No ID found')
|
||||||
|
}
|
||||||
const { state: formState } = await getFormState({
|
const { state: formState } = await getFormState({
|
||||||
apiRoute: config.routes.api,
|
apiRoute: config.routes.api,
|
||||||
body: {
|
body: {
|
||||||
@@ -155,13 +159,13 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
|||||||
className={`${baseClass}__block-pill ${baseClass}__block-pill-${formData?.blockType}`}
|
className={`${baseClass}__block-pill ${baseClass}__block-pill-${formData?.blockType}`}
|
||||||
pillStyle="white"
|
pillStyle="white"
|
||||||
>
|
>
|
||||||
{clientBlock && typeof clientBlock.labels.singular === 'string'
|
{clientBlock && typeof clientBlock.labels?.singular === 'string'
|
||||||
? getTranslation(clientBlock.labels.singular, i18n)
|
? getTranslation(clientBlock.labels.singular, i18n)
|
||||||
: clientBlock.slug}
|
: clientBlock?.slug}
|
||||||
</Pill>
|
</Pill>
|
||||||
<SectionTitle
|
<SectionTitle
|
||||||
path="blockName"
|
path="blockName"
|
||||||
readOnly={parentLexicalRichTextField?.admin?.readOnly}
|
readOnly={parentLexicalRichTextField?.admin?.readOnly || false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import './index.scss'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
readonly formData: InlineBlockFields
|
readonly formData: InlineBlockFields
|
||||||
readonly nodeKey?: string
|
readonly nodeKey: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InlineBlockComponent: React.FC<Props> = (props) => {
|
export const InlineBlockComponent: React.FC<Props> = (props) => {
|
||||||
@@ -45,12 +45,12 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
|||||||
} = useEditorConfigContext()
|
} = useEditorConfigContext()
|
||||||
|
|
||||||
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_inline_blocks`
|
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_inline_blocks`
|
||||||
const blocksField: BlocksFieldClient = richTextComponentMap.get(componentMapRenderedBlockPath)[0]
|
const blocksField: BlocksFieldClient = richTextComponentMap?.get(componentMapRenderedBlockPath)[0]
|
||||||
const clientBlock = blocksField.blocks.find((block) => block.slug === formData.blockType)
|
const clientBlock = blocksField.blocks.find((block) => block.slug === formData.blockType)
|
||||||
|
|
||||||
const removeInlineBlock = useCallback(() => {
|
const removeInlineBlock = useCallback(() => {
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
$getNodeByKey(nodeKey).remove()
|
$getNodeByKey(nodeKey)?.remove()
|
||||||
})
|
})
|
||||||
}, [editor, nodeKey])
|
}, [editor, nodeKey])
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const blockDisplayName = clientBlock?.labels?.singular
|
const blockDisplayName = clientBlock?.labels?.singular
|
||||||
? getTranslation(clientBlock.labels.singular, i18n)
|
? getTranslation(clientBlock.labels.singular, i18n)
|
||||||
: clientBlock.slug
|
: clientBlock?.slug
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -122,7 +122,7 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
|
|||||||
mappedComponent={clientBlock.admin.components.Label}
|
mappedComponent={clientBlock.admin.components.Label}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div>{getTranslation(clientBlock.labels?.singular, i18n)}</div>
|
<div>{getTranslation(clientBlock!.labels!.singular, i18n)}</div>
|
||||||
)}
|
)}
|
||||||
{editor.isEditable() && (
|
{editor.isEditable() && (
|
||||||
<div className={`${baseClass}__actions`}>
|
<div className={`${baseClass}__actions`}>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export type BlocksFeatureClientProps = {
|
|||||||
clientBlockSlugs: string[]
|
clientBlockSlugs: string[]
|
||||||
clientInlineBlockSlugs: string[]
|
clientInlineBlockSlugs: string[]
|
||||||
}
|
}
|
||||||
|
// @ts-expect-error - TODO: fix this
|
||||||
export const BlocksFeatureClient = createClientFeature<BlocksFeatureClientProps>(({ props }) => ({
|
export const BlocksFeatureClient = createClientFeature<BlocksFeatureClientProps>(({ props }) => ({
|
||||||
nodes: [BlockNode, InlineBlockNode],
|
nodes: [BlockNode, InlineBlockNode],
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -39,22 +39,25 @@ export const BlocksFeatureClient = createClientFeature<BlocksFeatureClientProps>
|
|||||||
key: 'block-' + blockSlug,
|
key: 'block-' + blockSlug,
|
||||||
keywords: ['block', 'blocks', blockSlug],
|
keywords: ['block', 'blocks', blockSlug],
|
||||||
label: ({ i18n, richTextComponentMap }) => {
|
label: ({ i18n, richTextComponentMap }) => {
|
||||||
|
if (!richTextComponentMap) {
|
||||||
|
return blockSlug
|
||||||
|
}
|
||||||
|
|
||||||
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_blocks`
|
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_blocks`
|
||||||
const blocksField: BlocksFieldClient = richTextComponentMap.get(
|
const blocksField: BlocksFieldClient = richTextComponentMap.get(
|
||||||
componentMapRenderedBlockPath,
|
componentMapRenderedBlockPath,
|
||||||
)[0]
|
)?.[0]
|
||||||
|
|
||||||
const clientBlock = blocksField.blocks.find((_block) => _block.slug === blockSlug)
|
const clientBlock = blocksField.blocks.find((_block) => _block.slug === blockSlug)
|
||||||
|
|
||||||
const blockDisplayName = clientBlock.labels.singular
|
const blockDisplayName = clientBlock?.labels?.singular
|
||||||
? getTranslation(clientBlock.labels.singular, i18n)
|
? getTranslation(clientBlock.labels.singular, i18n)
|
||||||
: clientBlock.slug
|
: clientBlock?.slug
|
||||||
|
|
||||||
return blockDisplayName
|
return blockDisplayName
|
||||||
},
|
},
|
||||||
onSelect: ({ editor }) => {
|
onSelect: ({ editor }) => {
|
||||||
editor.dispatchCommand(INSERT_BLOCK_COMMAND, {
|
editor.dispatchCommand(INSERT_BLOCK_COMMAND, {
|
||||||
id: null,
|
|
||||||
blockName: '',
|
blockName: '',
|
||||||
blockType: blockSlug,
|
blockType: blockSlug,
|
||||||
})
|
})
|
||||||
@@ -75,26 +78,29 @@ export const BlocksFeatureClient = createClientFeature<BlocksFeatureClientProps>
|
|||||||
key: 'inlineBlocks-' + inlineBlockSlug,
|
key: 'inlineBlocks-' + inlineBlockSlug,
|
||||||
keywords: ['inlineBlock', 'inline block', inlineBlockSlug],
|
keywords: ['inlineBlock', 'inline block', inlineBlockSlug],
|
||||||
label: ({ i18n, richTextComponentMap }) => {
|
label: ({ i18n, richTextComponentMap }) => {
|
||||||
|
if (!richTextComponentMap) {
|
||||||
|
return inlineBlockSlug
|
||||||
|
}
|
||||||
|
|
||||||
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_inline_blocks`
|
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_inline_blocks`
|
||||||
|
|
||||||
const blocksField: BlocksFieldClient = richTextComponentMap.get(
|
const blocksField: BlocksFieldClient = richTextComponentMap.get(
|
||||||
componentMapRenderedBlockPath,
|
componentMapRenderedBlockPath,
|
||||||
)[0]
|
)?.[0]
|
||||||
|
|
||||||
const clientBlock = blocksField.blocks.find(
|
const clientBlock = blocksField.blocks.find(
|
||||||
(_block) => _block.slug === inlineBlockSlug,
|
(_block) => _block.slug === inlineBlockSlug,
|
||||||
)
|
)
|
||||||
|
|
||||||
const blockDisplayName = clientBlock.labels.singular
|
const blockDisplayName = clientBlock?.labels?.singular
|
||||||
? getTranslation(clientBlock.labels.singular, i18n)
|
? getTranslation(clientBlock.labels.singular, i18n)
|
||||||
: clientBlock.slug
|
: clientBlock?.slug
|
||||||
|
|
||||||
return blockDisplayName
|
return blockDisplayName
|
||||||
},
|
},
|
||||||
onSelect: ({ editor }) => {
|
onSelect: ({ editor }) => {
|
||||||
editor.dispatchCommand(OPEN_INLINE_BLOCK_DRAWER_COMMAND, {
|
editor.dispatchCommand(OPEN_INLINE_BLOCK_DRAWER_COMMAND, {
|
||||||
fields: {
|
fields: {
|
||||||
id: null,
|
|
||||||
blockName: '',
|
blockName: '',
|
||||||
blockType: inlineBlockSlug,
|
blockType: inlineBlockSlug,
|
||||||
},
|
},
|
||||||
@@ -122,22 +128,24 @@ export const BlocksFeatureClient = createClientFeature<BlocksFeatureClientProps>
|
|||||||
isActive: undefined, // At this point, we would be inside a sub-richtext-editor. And at this point this will be run against the focused sub-editor, not the parent editor which has the actual block. Thus, no point in running this
|
isActive: undefined, // At this point, we would be inside a sub-richtext-editor. And at this point this will be run against the focused sub-editor, not the parent editor which has the actual block. Thus, no point in running this
|
||||||
key: 'block-' + blockSlug,
|
key: 'block-' + blockSlug,
|
||||||
label: ({ i18n, richTextComponentMap }) => {
|
label: ({ i18n, richTextComponentMap }) => {
|
||||||
|
if (!richTextComponentMap) {
|
||||||
|
return blockSlug
|
||||||
|
}
|
||||||
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_blocks`
|
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_blocks`
|
||||||
const blocksField: BlocksFieldClient = richTextComponentMap.get(
|
const blocksField: BlocksFieldClient = richTextComponentMap.get(
|
||||||
componentMapRenderedBlockPath,
|
componentMapRenderedBlockPath,
|
||||||
)[0]
|
)?.[0]
|
||||||
|
|
||||||
const clientBlock = blocksField.blocks.find((_block) => _block.slug === blockSlug)
|
const clientBlock = blocksField.blocks.find((_block) => _block.slug === blockSlug)
|
||||||
|
|
||||||
const blockDisplayName = clientBlock.labels.singular
|
const blockDisplayName = clientBlock?.labels?.singular
|
||||||
? getTranslation(clientBlock.labels.singular, i18n)
|
? getTranslation(clientBlock.labels.singular, i18n)
|
||||||
: clientBlock.slug
|
: clientBlock?.slug
|
||||||
|
|
||||||
return blockDisplayName
|
return blockDisplayName
|
||||||
},
|
},
|
||||||
onSelect: ({ editor }) => {
|
onSelect: ({ editor }) => {
|
||||||
editor.dispatchCommand(INSERT_BLOCK_COMMAND, {
|
editor.dispatchCommand(INSERT_BLOCK_COMMAND, {
|
||||||
id: null,
|
|
||||||
blockName: '',
|
blockName: '',
|
||||||
blockType: blockSlug,
|
blockType: blockSlug,
|
||||||
})
|
})
|
||||||
@@ -159,18 +167,22 @@ export const BlocksFeatureClient = createClientFeature<BlocksFeatureClientProps>
|
|||||||
isActive: undefined,
|
isActive: undefined,
|
||||||
key: 'inlineBlock-' + inlineBlockSlug,
|
key: 'inlineBlock-' + inlineBlockSlug,
|
||||||
label: ({ i18n, richTextComponentMap }) => {
|
label: ({ i18n, richTextComponentMap }) => {
|
||||||
|
if (!richTextComponentMap) {
|
||||||
|
return inlineBlockSlug
|
||||||
|
}
|
||||||
|
|
||||||
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_inline_blocks`
|
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_inline_blocks`
|
||||||
const blocksField: BlocksFieldClient = richTextComponentMap.get(
|
const blocksField: BlocksFieldClient = richTextComponentMap.get(
|
||||||
componentMapRenderedBlockPath,
|
componentMapRenderedBlockPath,
|
||||||
)[0]
|
)?.[0]
|
||||||
|
|
||||||
const clientBlock = blocksField.blocks.find(
|
const clientBlock = blocksField.blocks.find(
|
||||||
(_block) => _block.slug === inlineBlockSlug,
|
(_block) => _block.slug === inlineBlockSlug,
|
||||||
)
|
)
|
||||||
|
|
||||||
const blockDisplayName = clientBlock.labels.singular
|
const blockDisplayName = clientBlock?.labels?.singular
|
||||||
? getTranslation(clientBlock.labels.singular, i18n)
|
? getTranslation(clientBlock.labels.singular, i18n)
|
||||||
: clientBlock.slug
|
: clientBlock?.slug
|
||||||
|
|
||||||
return blockDisplayName
|
return blockDisplayName
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import type { EditorConfig, LexicalEditor, LexicalNode } from 'lexical'
|
|||||||
import ObjectID from 'bson-objectid'
|
import ObjectID from 'bson-objectid'
|
||||||
import React, { type JSX } from 'react'
|
import React, { type JSX } from 'react'
|
||||||
|
|
||||||
import type { BlockFields, SerializedBlockNode } from '../../server/nodes/BlocksNode.js'
|
import type {
|
||||||
|
BlockFields,
|
||||||
|
BlockFieldsOptionalID,
|
||||||
|
SerializedBlockNode,
|
||||||
|
} from '../../server/nodes/BlocksNode.js'
|
||||||
|
|
||||||
import { ServerBlockNode } from '../../server/nodes/BlocksNode.js'
|
import { ServerBlockNode } from '../../server/nodes/BlocksNode.js'
|
||||||
|
|
||||||
@@ -48,7 +52,7 @@ export class BlockNode extends ServerBlockNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $createBlockNode(fields: Exclude<BlockFields, 'id'>): BlockNode {
|
export function $createBlockNode(fields: BlockFieldsOptionalID): BlockNode {
|
||||||
return new BlockNode({
|
return new BlockNode({
|
||||||
fields: {
|
fields: {
|
||||||
...fields,
|
...fields,
|
||||||
|
|||||||
@@ -27,26 +27,25 @@ import {
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import type { PluginComponent } from '../../../typesClient.js'
|
import type { PluginComponent } from '../../../typesClient.js'
|
||||||
import type { BlockFields } from '../../server/nodes/BlocksNode.js'
|
import type { BlockFields, BlockFieldsOptionalID } from '../../server/nodes/BlocksNode.js'
|
||||||
import type { BlocksFeatureClientProps } from '../index.js'
|
import type { BlocksFeatureClientProps } from '../index.js'
|
||||||
import type { InlineBlockNode } from '../nodes/InlineBlocksNode.js'
|
|
||||||
|
|
||||||
import { useEditorConfigContext } from '../../../../lexical/config/client/EditorConfigProvider.js'
|
import { useEditorConfigContext } from '../../../../lexical/config/client/EditorConfigProvider.js'
|
||||||
import { FieldsDrawer } from '../../../../utilities/fieldsDrawer/Drawer.js'
|
import { FieldsDrawer } from '../../../../utilities/fieldsDrawer/Drawer.js'
|
||||||
import { $createBlockNode, BlockNode } from '../nodes/BlocksNode.js'
|
import { $createBlockNode, BlockNode } from '../nodes/BlocksNode.js'
|
||||||
import { $createInlineBlockNode } from '../nodes/InlineBlocksNode.js'
|
import { $createInlineBlockNode, $isInlineBlockNode } from '../nodes/InlineBlocksNode.js'
|
||||||
import {
|
import {
|
||||||
INSERT_BLOCK_COMMAND,
|
INSERT_BLOCK_COMMAND,
|
||||||
INSERT_INLINE_BLOCK_COMMAND,
|
INSERT_INLINE_BLOCK_COMMAND,
|
||||||
OPEN_INLINE_BLOCK_DRAWER_COMMAND,
|
OPEN_INLINE_BLOCK_DRAWER_COMMAND,
|
||||||
} from './commands.js'
|
} from './commands.js'
|
||||||
|
|
||||||
export type InsertBlockPayload = Exclude<BlockFields, 'id'>
|
export type InsertBlockPayload = BlockFieldsOptionalID
|
||||||
|
|
||||||
export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
|
export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
|
||||||
const [editor] = useLexicalComposerContext()
|
const [editor] = useLexicalComposerContext()
|
||||||
const { closeModal, toggleModal } = useModal()
|
const { closeModal, toggleModal } = useModal()
|
||||||
const [blockFields, setBlockFields] = useState<BlockFields>(null)
|
const [blockFields, setBlockFields] = useState<BlockFields | null>(null)
|
||||||
const [blockType, setBlockType] = useState<string>('' as any)
|
const [blockType, setBlockType] = useState<string>('' as any)
|
||||||
const [targetNodeKey, setTargetNodeKey] = useState<null | string>(null)
|
const [targetNodeKey, setTargetNodeKey] = useState<null | string>(null)
|
||||||
const { i18n, t } = useTranslation<string, any>()
|
const { i18n, t } = useTranslation<string, any>()
|
||||||
@@ -89,7 +88,7 @@ export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
|
|||||||
$isParagraphNode(focusNode) &&
|
$isParagraphNode(focusNode) &&
|
||||||
focusNode.getTextContentSize() === 0 &&
|
focusNode.getTextContentSize() === 0 &&
|
||||||
focusNode
|
focusNode
|
||||||
.getParent()
|
.getParentOrThrow()
|
||||||
.getChildren()
|
.getChildren()
|
||||||
.filter((node) => $isParagraphNode(node)).length > 1
|
.filter((node) => $isParagraphNode(node)).length > 1
|
||||||
) {
|
) {
|
||||||
@@ -106,9 +105,9 @@ export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
|
|||||||
INSERT_INLINE_BLOCK_COMMAND,
|
INSERT_INLINE_BLOCK_COMMAND,
|
||||||
(fields) => {
|
(fields) => {
|
||||||
if (targetNodeKey) {
|
if (targetNodeKey) {
|
||||||
const node: InlineBlockNode = $getNodeByKey(targetNodeKey)
|
const node = $getNodeByKey(targetNodeKey)
|
||||||
|
|
||||||
if (!node) {
|
if (!node || !$isInlineBlockNode(node)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +166,7 @@ export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
|
|||||||
const schemaFieldsPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_inline_blocks.lexical_inline_blocks.${blockFields?.blockType}`
|
const schemaFieldsPath = `${schemaPath}.lexical_internal_feature.blocks.lexical_inline_blocks.lexical_inline_blocks.${blockFields?.blockType}`
|
||||||
|
|
||||||
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_inline_blocks`
|
const componentMapRenderedBlockPath = `lexical_internal_feature.blocks.fields.lexical_inline_blocks`
|
||||||
const blocksField: BlocksFieldClient = richTextComponentMap.has(componentMapRenderedBlockPath)
|
const blocksField: BlocksFieldClient = richTextComponentMap?.has(componentMapRenderedBlockPath)
|
||||||
? richTextComponentMap.get(componentMapRenderedBlockPath)[0]
|
? richTextComponentMap.get(componentMapRenderedBlockPath)[0]
|
||||||
: null
|
: null
|
||||||
|
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ export const BlocksFeature = createServerFeature<
|
|||||||
i18n,
|
i18n,
|
||||||
nodes: [
|
nodes: [
|
||||||
createNode({
|
createNode({
|
||||||
|
// @ts-expect-error - TODO: fix this
|
||||||
getSubFields: ({ node }) => {
|
getSubFields: ({ node }) => {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
if (props?.blocks?.length) {
|
if (props?.blocks?.length) {
|
||||||
@@ -145,7 +146,7 @@ export const BlocksFeature = createServerFeature<
|
|||||||
|
|
||||||
const blockType = node.fields.blockType
|
const blockType = node.fields.blockType
|
||||||
|
|
||||||
const block = props.blocks.find((block) => block.slug === blockType)
|
const block = props.blocks?.find((block) => block.slug === blockType)
|
||||||
return block?.fields
|
return block?.fields
|
||||||
},
|
},
|
||||||
getSubFieldsData: ({ node }) => {
|
getSubFieldsData: ({ node }) => {
|
||||||
@@ -156,6 +157,7 @@ export const BlocksFeature = createServerFeature<
|
|||||||
validations: [blockValidationHOC(props.blocks)],
|
validations: [blockValidationHOC(props.blocks)],
|
||||||
}),
|
}),
|
||||||
createNode({
|
createNode({
|
||||||
|
// @ts-expect-error - TODO: fix this
|
||||||
getSubFields: ({ node }) => {
|
getSubFields: ({ node }) => {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
if (props?.inlineBlocks?.length) {
|
if (props?.inlineBlocks?.length) {
|
||||||
@@ -172,7 +174,7 @@ export const BlocksFeature = createServerFeature<
|
|||||||
|
|
||||||
const blockType = node.fields.blockType
|
const blockType = node.fields.blockType
|
||||||
|
|
||||||
const block = props.inlineBlocks.find((block) => block.slug === blockType)
|
const block = props.inlineBlocks?.find((block) => block.slug === blockType)
|
||||||
return block?.fields
|
return block?.fields
|
||||||
},
|
},
|
||||||
getSubFieldsData: ({ node }) => {
|
getSubFieldsData: ({ node }) => {
|
||||||
|
|||||||
@@ -16,13 +16,20 @@ import { DecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode.js'
|
|||||||
import ObjectID from 'bson-objectid'
|
import ObjectID from 'bson-objectid'
|
||||||
import { deepCopyObjectSimple } from 'payload/shared'
|
import { deepCopyObjectSimple } from 'payload/shared'
|
||||||
|
|
||||||
export type BlockFields<TBlockFields extends JsonObject = JsonObject> = {
|
type BaseBlockFields<TBlockFields extends JsonObject = JsonObject> = {
|
||||||
/** Block form data */
|
/** Block form data */
|
||||||
blockName: string
|
blockName: string
|
||||||
blockType: string
|
blockType: string
|
||||||
id: string
|
|
||||||
} & TBlockFields
|
} & TBlockFields
|
||||||
|
|
||||||
|
export type BlockFields<TBlockFields extends JsonObject = JsonObject> = {
|
||||||
|
id: string
|
||||||
|
} & BaseBlockFields<TBlockFields>
|
||||||
|
|
||||||
|
export type BlockFieldsOptionalID<TBlockFields extends JsonObject = JsonObject> = {
|
||||||
|
id?: string
|
||||||
|
} & BaseBlockFields<TBlockFields>
|
||||||
|
|
||||||
export type SerializedBlockNode<TBlockFields extends JsonObject = JsonObject> = Spread<
|
export type SerializedBlockNode<TBlockFields extends JsonObject = JsonObject> = Spread<
|
||||||
{
|
{
|
||||||
children?: never // required so that our typed editor state doesn't automatically add children
|
children?: never // required so that our typed editor state doesn't automatically add children
|
||||||
@@ -84,7 +91,7 @@ export class ServerBlockNode extends DecoratorBlockNode {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element {
|
decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element | null {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +128,7 @@ export class ServerBlockNode extends DecoratorBlockNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $createServerBlockNode(fields: Exclude<BlockFields, 'id'>): ServerBlockNode {
|
export function $createServerBlockNode(fields: BlockFieldsOptionalID): ServerBlockNode {
|
||||||
return new ServerBlockNode({
|
return new ServerBlockNode({
|
||||||
fields: {
|
fields: {
|
||||||
...fields,
|
...fields,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export type SerializedServerInlineBlockNode = Spread<
|
|||||||
SerializedLexicalNode
|
SerializedLexicalNode
|
||||||
>
|
>
|
||||||
|
|
||||||
export class ServerInlineBlockNode extends DecoratorNode<React.ReactElement> {
|
export class ServerInlineBlockNode extends DecoratorNode<null | React.ReactElement> {
|
||||||
__fields: InlineBlockFields
|
__fields: InlineBlockFields
|
||||||
|
|
||||||
constructor({ fields, key }: { fields: InlineBlockFields; key?: NodeKey }) {
|
constructor({ fields, key }: { fields: InlineBlockFields; key?: NodeKey }) {
|
||||||
@@ -74,7 +74,7 @@ export class ServerInlineBlockNode extends DecoratorNode<React.ReactElement> {
|
|||||||
return element
|
return element
|
||||||
}
|
}
|
||||||
|
|
||||||
decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element {
|
decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element | null {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const blockValidationHOC = (
|
|||||||
siblingData: blockFieldData,
|
siblingData: blockFieldData,
|
||||||
})
|
})
|
||||||
|
|
||||||
let errorPaths = []
|
let errorPaths: string[] = []
|
||||||
for (const fieldKey in result) {
|
for (const fieldKey in result) {
|
||||||
if (result[fieldKey].errorPaths) {
|
if (result[fieldKey].errorPaths) {
|
||||||
errorPaths = errorPaths.concat(result[fieldKey].errorPaths)
|
errorPaths = errorPaths.concat(result[fieldKey].errorPaths)
|
||||||
|
|||||||
@@ -69,12 +69,12 @@ export async function convertLexicalToHTML({
|
|||||||
return await convertLexicalNodesToHTML({
|
return await convertLexicalNodesToHTML({
|
||||||
converters,
|
converters,
|
||||||
currentDepth,
|
currentDepth,
|
||||||
depth,
|
depth: depth!,
|
||||||
draft: draft === undefined ? false : draft,
|
draft: draft === undefined ? false : draft,
|
||||||
lexicalNodes: data?.root?.children,
|
lexicalNodes: data?.root?.children,
|
||||||
overrideAccess: overrideAccess === undefined ? false : overrideAccess,
|
overrideAccess: overrideAccess === undefined ? false : overrideAccess,
|
||||||
parent: data?.root,
|
parent: data?.root,
|
||||||
req,
|
req: req!,
|
||||||
showHiddenFields: showHiddenFields === undefined ? false : showHiddenFields,
|
showHiddenFields: showHiddenFields === undefined ? false : showHiddenFields,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ function findFieldPathAndSiblingFields(
|
|||||||
): {
|
): {
|
||||||
path: string[]
|
path: string[]
|
||||||
siblingFields: Field[]
|
siblingFields: Field[]
|
||||||
} {
|
} | null {
|
||||||
for (const curField of fields) {
|
for (const curField of fields) {
|
||||||
if (curField === field) {
|
if (curField === field) {
|
||||||
return {
|
return {
|
||||||
@@ -172,7 +172,7 @@ export const lexicalHTML: (
|
|||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
siblingData,
|
siblingData,
|
||||||
}) => {
|
}) => {
|
||||||
const fields = collection ? collection.fields : global.fields
|
const fields = collection ? collection.fields : global!.fields
|
||||||
|
|
||||||
const foundSiblingFields = findFieldPathAndSiblingFields(fields, [], field)
|
const foundSiblingFields = findFieldPathAndSiblingFields(fields, [], field)
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ function sanitizeSelection(selection: Selection) {
|
|||||||
|
|
||||||
function getPathFromNodeToEditor(node: Node, rootElement: HTMLElement | null) {
|
function getPathFromNodeToEditor(node: Node, rootElement: HTMLElement | null) {
|
||||||
let currentNode: Node | null | undefined = node
|
let currentNode: Node | null | undefined = node
|
||||||
const path = []
|
const path: number[] = []
|
||||||
while (currentNode !== rootElement) {
|
while (currentNode !== rootElement) {
|
||||||
if (currentNode !== null && currentNode !== undefined) {
|
if (currentNode !== null && currentNode !== undefined) {
|
||||||
path.unshift(
|
path.unshift(
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ function computeSelectionCount(selection: TableSelection): {
|
|||||||
function isTableSelectionRectangular(selection: TableSelection): boolean {
|
function isTableSelectionRectangular(selection: TableSelection): boolean {
|
||||||
const nodes = selection.getNodes()
|
const nodes = selection.getNodes()
|
||||||
const currentRows: Array<number> = []
|
const currentRows: Array<number> = []
|
||||||
let currentRow = null
|
let currentRow: null | TableRowNode = null
|
||||||
let expectedColumns = null
|
let expectedColumns: null | number = null
|
||||||
let currentColumns = 0
|
let currentColumns = 0
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
const node = nodes[i]
|
const node = nodes[i]
|
||||||
|
|||||||
@@ -138,8 +138,8 @@ export const EXPERIMENTAL_TableFeature = createServerFeature({
|
|||||||
const backgroundColor = node.backgroundColor
|
const backgroundColor = node.backgroundColor
|
||||||
? `background-color: ${node.backgroundColor};`
|
? `background-color: ${node.backgroundColor};`
|
||||||
: ''
|
: ''
|
||||||
const colSpan = node.colSpan > 1 ? `colspan="${node.colSpan}"` : ''
|
const colSpan = node.colSpan && node.colSpan > 1 ? `colspan="${node.colSpan}"` : ''
|
||||||
const rowSpan = node.rowSpan > 1 ? `rowspan="${node.rowSpan}"` : ''
|
const rowSpan = node.rowSpan && node.rowSpan > 1 ? `rowspan="${node.rowSpan}"` : ''
|
||||||
|
|
||||||
return `<${tagName} class="lexical-table-cell ${headerStateClass}" style="border: 1px solid #ccc; padding: 8px; ${backgroundColor}" ${colSpan} ${rowSpan}>${childrenText}</${tagName}>`
|
return `<${tagName} class="lexical-table-cell ${headerStateClass}" style="border: 1px solid #ccc; padding: 8px; ${backgroundColor}" ${colSpan} ${rowSpan}>${childrenText}</${tagName}>`
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const INSERT_HORIZONTAL_RULE_COMMAND: LexicalCommand<void> = createComman
|
|||||||
*
|
*
|
||||||
* If we used DecoratorBlockNode instead, we would only need a decorate method
|
* If we used DecoratorBlockNode instead, we would only need a decorate method
|
||||||
*/
|
*/
|
||||||
export class HorizontalRuleServerNode extends DecoratorNode<React.ReactElement> {
|
export class HorizontalRuleServerNode extends DecoratorNode<null | React.ReactElement> {
|
||||||
static clone(node: HorizontalRuleServerNode): HorizontalRuleServerNode {
|
static clone(node: HorizontalRuleServerNode): HorizontalRuleServerNode {
|
||||||
return new this(node.__key)
|
return new this(node.__key)
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ export class HorizontalRuleServerNode extends DecoratorNode<React.ReactElement>
|
|||||||
return element
|
return element
|
||||||
}
|
}
|
||||||
|
|
||||||
decorate() {
|
decorate(): null | React.ReactElement {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ const toolbarGroups: ToolbarGroup[] = [
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for (const node of selection.getNodes()) {
|
for (const node of selection.getNodes()) {
|
||||||
|
const parent = node.getParentOrThrow()
|
||||||
// If at least one node is indented, this should be active
|
// If at least one node is indented, this should be active
|
||||||
if (
|
if (
|
||||||
('__indent' in node && (node.__indent as number) > 0) ||
|
('__indent' in node && (node.__indent as number) > 0) ||
|
||||||
(node.getParent() && '__indent' in node.getParent() && node.getParent().__indent > 0)
|
('__indent' in parent && parent.__indent > 0)
|
||||||
) {
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const toolbarGroups: ToolbarGroup[] = [
|
|||||||
},
|
},
|
||||||
onSelect: ({ editor, isActive }) => {
|
onSelect: ({ editor, isActive }) => {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
let selectedText: string = null
|
let selectedText: string | undefined
|
||||||
let selectedNodes: LexicalNode[] = []
|
let selectedNodes: LexicalNode[] = []
|
||||||
editor.getEditorState().read(() => {
|
editor.getEditorState().read(() => {
|
||||||
selectedText = $getSelection()?.getTextContent()
|
selectedText = $getSelection()?.getTextContent()
|
||||||
|
|||||||
@@ -179,11 +179,11 @@ function $createAutoLinkNode_(
|
|||||||
endIndex: number,
|
endIndex: number,
|
||||||
match: LinkMatcherResult,
|
match: LinkMatcherResult,
|
||||||
): TextNode | undefined {
|
): TextNode | undefined {
|
||||||
const fields: LinkFields = {
|
const fields = {
|
||||||
linkType: 'custom',
|
linkType: 'custom',
|
||||||
url: match.url,
|
url: match.url,
|
||||||
...match.fields,
|
...match.fields,
|
||||||
}
|
} as LinkFields
|
||||||
|
|
||||||
const linkNode = $createAutoLinkNode({ fields })
|
const linkNode = $createAutoLinkNode({ fields })
|
||||||
if (nodes.length === 1) {
|
if (nodes.length === 1) {
|
||||||
@@ -210,7 +210,7 @@ function $createAutoLinkNode_(
|
|||||||
} else {
|
} else {
|
||||||
;[, firstLinkTextNode] = firstTextNode.splitText(startIndex)
|
;[, firstLinkTextNode] = firstTextNode.splitText(startIndex)
|
||||||
}
|
}
|
||||||
const linkNodes = []
|
const linkNodes: LexicalNode[] = []
|
||||||
let remainingTextNode
|
let remainingTextNode
|
||||||
for (let i = 1; i < nodes.length; i++) {
|
for (let i = 1; i < nodes.length; i++) {
|
||||||
const currentNode = nodes[i]
|
const currentNode = nodes[i]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { LexicalNode } from 'lexical'
|
import type { ElementNode, LexicalNode } from 'lexical'
|
||||||
import type { Data, FormState } from 'payload'
|
import type { Data, FormState } from 'payload'
|
||||||
|
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
|
||||||
@@ -41,8 +41,8 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
|
|||||||
const [editor] = useLexicalComposerContext()
|
const [editor] = useLexicalComposerContext()
|
||||||
|
|
||||||
const editorRef = useRef<HTMLDivElement | null>(null)
|
const editorRef = useRef<HTMLDivElement | null>(null)
|
||||||
const [linkUrl, setLinkUrl] = useState(null)
|
const [linkUrl, setLinkUrl] = useState<null | string>(null)
|
||||||
const [linkLabel, setLinkLabel] = useState(null)
|
const [linkLabel, setLinkLabel] = useState<null | string>(null)
|
||||||
|
|
||||||
const { uuid } = useEditorConfigContext()
|
const { uuid } = useEditorConfigContext()
|
||||||
|
|
||||||
@@ -50,7 +50,9 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
|
|||||||
|
|
||||||
const { i18n, t } = useTranslation()
|
const { i18n, t } = useTranslation()
|
||||||
|
|
||||||
const [stateData, setStateData] = useState<{ id?: string; text: string } & LinkFields>(null)
|
const [stateData, setStateData] = useState<
|
||||||
|
({ id?: string; text: string } & LinkFields) | undefined
|
||||||
|
>()
|
||||||
|
|
||||||
const { closeModal, toggleModal } = useModal()
|
const { closeModal, toggleModal } = useModal()
|
||||||
const editDepth = useEditDepth()
|
const editDepth = useEditDepth()
|
||||||
@@ -74,12 +76,12 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
|
|||||||
setLinkUrl(null)
|
setLinkUrl(null)
|
||||||
setLinkLabel(null)
|
setLinkLabel(null)
|
||||||
setSelectedNodes([])
|
setSelectedNodes([])
|
||||||
setStateData(null)
|
setStateData(undefined)
|
||||||
}, [setIsLink, setLinkUrl, setLinkLabel, setSelectedNodes])
|
}, [setIsLink, setLinkUrl, setLinkLabel, setSelectedNodes])
|
||||||
|
|
||||||
const $updateLinkEditor = useCallback(() => {
|
const $updateLinkEditor = useCallback(() => {
|
||||||
const selection = $getSelection()
|
const selection = $getSelection()
|
||||||
let selectedNodeDomRect: DOMRect | undefined = null
|
let selectedNodeDomRect: DOMRect | undefined
|
||||||
|
|
||||||
if (!$isRangeSelection(selection) || !selection) {
|
if (!$isRangeSelection(selection) || !selection) {
|
||||||
setNotLink()
|
setNotLink()
|
||||||
@@ -90,7 +92,7 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
|
|||||||
|
|
||||||
const focusNode = getSelectedNode(selection)
|
const focusNode = getSelectedNode(selection)
|
||||||
selectedNodeDomRect = editor.getElementByKey(focusNode.getKey())?.getBoundingClientRect()
|
selectedNodeDomRect = editor.getElementByKey(focusNode.getKey())?.getBoundingClientRect()
|
||||||
const focusLinkParent: LinkNode = $findMatchingParent(focusNode, $isLinkNode)
|
const focusLinkParent = $findMatchingParent(focusNode, $isLinkNode)
|
||||||
|
|
||||||
// Prevent link modal from showing if selection spans further than the link: https://github.com/facebook/lexical/issues/4064
|
// Prevent link modal from showing if selection spans further than the link: https://github.com/facebook/lexical/issues/4064
|
||||||
const badNode = selection
|
const badNode = selection
|
||||||
@@ -111,10 +113,6 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
|
|||||||
|
|
||||||
// Initial state:
|
// Initial state:
|
||||||
const data: { text: string } & LinkFields = {
|
const data: { text: string } & LinkFields = {
|
||||||
doc: undefined,
|
|
||||||
linkType: undefined,
|
|
||||||
newTab: undefined,
|
|
||||||
url: '',
|
|
||||||
...focusLinkParent.getFields(),
|
...focusLinkParent.getFields(),
|
||||||
id: focusLinkParent.getID(),
|
id: focusLinkParent.getID(),
|
||||||
text: focusLinkParent.getTextContent(),
|
text: focusLinkParent.getTextContent(),
|
||||||
@@ -343,7 +341,7 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
|
|||||||
// See: https://github.com/facebook/lexical/pull/5536. This updates autolink nodes to link nodes whenever a change was made (which is good!).
|
// See: https://github.com/facebook/lexical/pull/5536. This updates autolink nodes to link nodes whenever a change was made (which is good!).
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
const selection = $getSelection()
|
const selection = $getSelection()
|
||||||
let linkParent = null
|
let linkParent: ElementNode | null = null
|
||||||
if ($isRangeSelection(selection)) {
|
if ($isRangeSelection(selection)) {
|
||||||
linkParent = getSelectedNode(selection).getParent()
|
linkParent = getSelectedNode(selection).getParent()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { LinkNode } from './LinkNode.js'
|
|||||||
|
|
||||||
export class AutoLinkNode extends LinkNode {
|
export class AutoLinkNode extends LinkNode {
|
||||||
static clone(node: AutoLinkNode): AutoLinkNode {
|
static clone(node: AutoLinkNode): AutoLinkNode {
|
||||||
return new AutoLinkNode({ id: undefined, fields: node.__fields, key: node.__key })
|
return new AutoLinkNode({ id: '', fields: node.__fields, key: node.__key })
|
||||||
}
|
}
|
||||||
|
|
||||||
static getType(): string {
|
static getType(): string {
|
||||||
@@ -67,7 +67,7 @@ export class AutoLinkNode extends LinkNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function $createAutoLinkNode({ fields }: { fields: LinkFields }): AutoLinkNode {
|
export function $createAutoLinkNode({ fields }: { fields: LinkFields }): AutoLinkNode {
|
||||||
return $applyNodeReplacement(new AutoLinkNode({ id: undefined, fields }))
|
return $applyNodeReplacement(new AutoLinkNode({ id: '', fields }))
|
||||||
}
|
}
|
||||||
export function $isAutoLinkNode(node: LexicalNode | null | undefined): node is AutoLinkNode {
|
export function $isAutoLinkNode(node: LexicalNode | null | undefined): node is AutoLinkNode {
|
||||||
return node instanceof AutoLinkNode
|
return node instanceof AutoLinkNode
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export class LinkNode extends ElementNode {
|
|||||||
doc: null,
|
doc: null,
|
||||||
linkType: 'custom',
|
linkType: 'custom',
|
||||||
newTab: false,
|
newTab: false,
|
||||||
url: undefined,
|
url: '',
|
||||||
},
|
},
|
||||||
key,
|
key,
|
||||||
}: {
|
}: {
|
||||||
@@ -269,14 +269,14 @@ export const TOGGLE_LINK_COMMAND: LexicalCommand<LinkPayload | null> =
|
|||||||
export function $toggleLink(payload: LinkPayload): void {
|
export function $toggleLink(payload: LinkPayload): void {
|
||||||
const selection = $getSelection()
|
const selection = $getSelection()
|
||||||
|
|
||||||
if (!$isRangeSelection(selection) && !payload.selectedNodes.length) {
|
if (!$isRangeSelection(selection) && !payload.selectedNodes?.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const nodes = $isRangeSelection(selection) ? selection.extract() : payload.selectedNodes
|
const nodes = $isRangeSelection(selection) ? selection.extract() : payload.selectedNodes
|
||||||
|
|
||||||
if (payload === null) {
|
if (payload === null) {
|
||||||
// Remove LinkNodes
|
// Remove LinkNodes
|
||||||
nodes.forEach((node) => {
|
nodes?.forEach((node) => {
|
||||||
const parent = node.getParent()
|
const parent = node.getParent()
|
||||||
|
|
||||||
if ($isLinkNode(parent)) {
|
if ($isLinkNode(parent)) {
|
||||||
@@ -291,7 +291,7 @@ export function $toggleLink(payload: LinkPayload): void {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Add or merge LinkNodes
|
// Add or merge LinkNodes
|
||||||
if (nodes.length === 1) {
|
if (nodes?.length === 1) {
|
||||||
const firstNode = nodes[0]
|
const firstNode = nodes[0]
|
||||||
// if the first node is a LinkNode or if its
|
// if the first node is a LinkNode or if its
|
||||||
// parent is a LinkNode, we update the URL, target and rel.
|
// parent is a LinkNode, we update the URL, target and rel.
|
||||||
@@ -317,7 +317,7 @@ export function $toggleLink(payload: LinkPayload): void {
|
|||||||
let prevParent: ElementNodeType | LinkNode | null = null
|
let prevParent: ElementNodeType | LinkNode | null = null
|
||||||
let linkNode: LinkNode | null = null
|
let linkNode: LinkNode | null = null
|
||||||
|
|
||||||
nodes.forEach((node) => {
|
nodes?.forEach((node) => {
|
||||||
const parent = node.getParent()
|
const parent = node.getParent()
|
||||||
|
|
||||||
if (parent === linkNode || parent === null || ($isElementNode(node) && !node.isInline())) {
|
if (parent === linkNode || parent === null || ($isElementNode(node) && !node.isInline())) {
|
||||||
@@ -386,8 +386,12 @@ function $getAncestor(
|
|||||||
predicate: (ancestor: LexicalNode) => boolean,
|
predicate: (ancestor: LexicalNode) => boolean,
|
||||||
): LexicalNode | null {
|
): LexicalNode | null {
|
||||||
let parent: LexicalNode | null = node
|
let parent: LexicalNode | null = node
|
||||||
// eslint-disable-next-line no-empty
|
while (parent !== null) {
|
||||||
while (parent !== null && (parent = parent.getParent()) !== null && !predicate(parent)) {}
|
parent = parent.getParent()
|
||||||
|
if (parent === null || predicate(parent)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
return parent
|
return parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { SerializedElementNode, SerializedLexicalNode, Spread } from 'lexical'
|
import type { SerializedElementNode, SerializedLexicalNode, Spread } from 'lexical'
|
||||||
import type { JsonValue } from 'payload'
|
import type { DefaultDocumentIDType, JsonValue } from 'payload'
|
||||||
|
|
||||||
export type LinkFields = {
|
export type LinkFields = {
|
||||||
[key: string]: JsonValue
|
[key: string]: JsonValue
|
||||||
@@ -9,9 +9,9 @@ export type LinkFields = {
|
|||||||
| {
|
| {
|
||||||
// Actual doc data, populated in afterRead hook
|
// Actual doc data, populated in afterRead hook
|
||||||
[key: string]: JsonValue
|
[key: string]: JsonValue
|
||||||
id: string
|
id: DefaultDocumentIDType
|
||||||
}
|
}
|
||||||
| string
|
| DefaultDocumentIDType
|
||||||
} | null
|
} | null
|
||||||
linkType: 'custom' | 'internal'
|
linkType: 'custom' | 'internal'
|
||||||
newTab: boolean
|
newTab: boolean
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import { validateUrl, validateUrlMinimal } from '../../../lexical/utils/url.js'
|
|||||||
|
|
||||||
export const getBaseFields = (
|
export const getBaseFields = (
|
||||||
config: SanitizedConfig,
|
config: SanitizedConfig,
|
||||||
enabledCollections: CollectionSlug[],
|
enabledCollections?: CollectionSlug[],
|
||||||
disabledCollections: CollectionSlug[],
|
disabledCollections?: CollectionSlug[],
|
||||||
maxDepth?: number,
|
maxDepth?: number,
|
||||||
): FieldAffectingData[] => {
|
): FieldAffectingData[] => {
|
||||||
let enabledRelations: CollectionSlug[]
|
let enabledRelations: CollectionSlug[]
|
||||||
@@ -76,6 +76,7 @@ export const getBaseFields = (
|
|||||||
},
|
},
|
||||||
label: ({ t }) => t('fields:enterURL'),
|
label: ({ t }) => t('fields:enterURL'),
|
||||||
required: true,
|
required: true,
|
||||||
|
// @ts-expect-error - TODO: fix this
|
||||||
validate: (value: string) => {
|
validate: (value: string) => {
|
||||||
if (!validateUrlMinimal(value)) {
|
if (!validateUrlMinimal(value)) {
|
||||||
return 'Invalid URL'
|
return 'Invalid URL'
|
||||||
@@ -106,10 +107,12 @@ export const getBaseFields = (
|
|||||||
filterOptions:
|
filterOptions:
|
||||||
!enabledCollections && !disabledCollections
|
!enabledCollections && !disabledCollections
|
||||||
? ({ relationTo, user }) => {
|
? ({ relationTo, user }) => {
|
||||||
const hidden = config.collections.find(({ slug }) => slug === relationTo).admin.hidden
|
const hidden = config.collections.find(({ slug }) => slug === relationTo)?.admin
|
||||||
|
.hidden
|
||||||
if (typeof hidden === 'function' && hidden({ user } as { user: User })) {
|
if (typeof hidden === 'function' && hidden({ user } as { user: User })) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
label: ({ t }) => t('fields:chooseDocumentToLink'),
|
label: ({ t }) => t('fields:chooseDocumentToLink'),
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import type { CollectionSlug, Config, Field, FieldAffectingData, SanitizedConfig } from 'payload'
|
import type {
|
||||||
|
CollectionSlug,
|
||||||
|
Config,
|
||||||
|
DefaultDocumentIDType,
|
||||||
|
Field,
|
||||||
|
FieldAffectingData,
|
||||||
|
SanitizedConfig,
|
||||||
|
} from 'payload'
|
||||||
|
|
||||||
import escapeHTML from 'escape-html'
|
import escapeHTML from 'escape-html'
|
||||||
import { sanitizeFields } from 'payload'
|
import { sanitizeFields } from 'payload'
|
||||||
@@ -71,7 +78,7 @@ export const LinkFeature = createServerFeature<
|
|||||||
const validRelationships = _config.collections.map((c) => c.slug) || []
|
const validRelationships = _config.collections.map((c) => c.slug) || []
|
||||||
|
|
||||||
const _transformedFields = transformExtraFields(
|
const _transformedFields = transformExtraFields(
|
||||||
deepCopyObject(props.fields),
|
props.fields ? deepCopyObject(props.fields) : null,
|
||||||
_config,
|
_config,
|
||||||
props.enabledCollections,
|
props.enabledCollections,
|
||||||
props.disabledCollections,
|
props.disabledCollections,
|
||||||
@@ -148,9 +155,9 @@ export const LinkFeature = createServerFeature<
|
|||||||
let href: string = node.fields.url
|
let href: string = node.fields.url
|
||||||
if (node.fields.linkType === 'internal') {
|
if (node.fields.linkType === 'internal') {
|
||||||
href =
|
href =
|
||||||
typeof node.fields.doc?.value === 'string'
|
typeof node.fields.doc?.value !== 'object'
|
||||||
? node.fields.doc?.value
|
? String(node.fields.doc?.value)
|
||||||
: node.fields.doc?.value?.id
|
: String(node.fields.doc?.value?.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<a href="${href}"${target}${rel}>${childrenText}</a>`
|
return `<a href="${href}"${target}${rel}>${childrenText}</a>`
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ export function transformExtraFields(
|
|||||||
config: SanitizedConfig
|
config: SanitizedConfig
|
||||||
defaultFields: FieldAffectingData[]
|
defaultFields: FieldAffectingData[]
|
||||||
}) => (Field | FieldAffectingData)[])
|
}) => (Field | FieldAffectingData)[])
|
||||||
| Field[],
|
| Field[]
|
||||||
|
| null,
|
||||||
config: SanitizedConfig,
|
config: SanitizedConfig,
|
||||||
enabledCollections?: CollectionSlug[],
|
enabledCollections?: CollectionSlug[],
|
||||||
disabledCollections?: CollectionSlug[],
|
disabledCollections?: CollectionSlug[],
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const linkValidation = (
|
|||||||
siblingData: node.fields,
|
siblingData: node.fields,
|
||||||
})
|
})
|
||||||
|
|
||||||
let errorPaths = []
|
let errorPaths: string[] = []
|
||||||
for (const fieldKey in result) {
|
for (const fieldKey in result) {
|
||||||
if (result[fieldKey].errorPaths) {
|
if (result[fieldKey].errorPaths) {
|
||||||
errorPaths = errorPaths.concat(result[fieldKey].errorPaths)
|
errorPaths = errorPaths.concat(result[fieldKey].errorPaths)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const listExport = (
|
|||||||
exportChildren: (node: ElementNode) => string,
|
exportChildren: (node: ElementNode) => string,
|
||||||
depth: number,
|
depth: number,
|
||||||
): string => {
|
): string => {
|
||||||
const output = []
|
const output: string[] = []
|
||||||
const children = listNode.getChildren()
|
const children = listNode.getChildren()
|
||||||
let index = 0
|
let index = 0
|
||||||
for (const listItemNode of children) {
|
for (const listItemNode of children) {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export function convertLexicalPluginNodesToLexical({
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
const unknownConverter = converters.find((converter) => converter.nodeTypes.includes('unknown'))
|
const unknownConverter = converters.find((converter) => converter.nodeTypes.includes('unknown'))
|
||||||
|
// @ts-expect-error - vestiges of the migration to strict mode. Probably not important enough in this file to fix
|
||||||
return (
|
return (
|
||||||
lexicalPluginNodes.map((lexicalPluginNode, i) => {
|
lexicalPluginNodes.map((lexicalPluginNode, i) => {
|
||||||
if (lexicalPluginNode.type === 'paragraph') {
|
if (lexicalPluginNode.type === 'paragraph') {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export class UnknownConvertedNode extends DecoratorNode<JSX.Element> {
|
|||||||
return element
|
return element
|
||||||
}
|
}
|
||||||
|
|
||||||
decorate(): JSX.Element | null {
|
decorate(): JSX.Element {
|
||||||
return <Component data={this.__data} />
|
return <Component data={this.__data} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const SlateBlockquoteConverter: SlateNodeConverter = {
|
|||||||
canContainParagraphs: false,
|
canContainParagraphs: false,
|
||||||
converters,
|
converters,
|
||||||
parentNodeType: 'quote',
|
parentNodeType: 'quote',
|
||||||
slateNodes: slateNode.children,
|
slateNodes: slateNode.children!,
|
||||||
}),
|
}),
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
format: '',
|
format: '',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const SlateHeadingConverter: SlateNodeConverter = {
|
|||||||
canContainParagraphs: false,
|
canContainParagraphs: false,
|
||||||
converters,
|
converters,
|
||||||
parentNodeType: 'heading',
|
parentNodeType: 'heading',
|
||||||
slateNodes: slateNode.children,
|
slateNodes: slateNode.children!,
|
||||||
}),
|
}),
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
format: '',
|
format: '',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const SlateLinkConverter: SlateNodeConverter = {
|
|||||||
canContainParagraphs: false,
|
canContainParagraphs: false,
|
||||||
converters,
|
converters,
|
||||||
parentNodeType: 'link',
|
parentNodeType: 'link',
|
||||||
slateNodes: slateNode.children,
|
slateNodes: slateNode.children!,
|
||||||
}),
|
}),
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
fields: {
|
fields: {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const SlateListItemConverter: SlateNodeConverter = {
|
|||||||
canContainParagraphs: false,
|
canContainParagraphs: false,
|
||||||
converters,
|
converters,
|
||||||
parentNodeType: 'listitem',
|
parentNodeType: 'listitem',
|
||||||
slateNodes: slateNode.children,
|
slateNodes: slateNode.children!,
|
||||||
}),
|
}),
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
format: '',
|
format: '',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const SlateOrderedListConverter: SlateNodeConverter = {
|
|||||||
canContainParagraphs: false,
|
canContainParagraphs: false,
|
||||||
converters,
|
converters,
|
||||||
parentNodeType: 'list',
|
parentNodeType: 'list',
|
||||||
slateNodes: slateNode.children,
|
slateNodes: slateNode.children!,
|
||||||
}),
|
}),
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
format: '',
|
format: '',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const SlateUnknownConverter: SlateNodeConverter = {
|
|||||||
canContainParagraphs: false,
|
canContainParagraphs: false,
|
||||||
converters,
|
converters,
|
||||||
parentNodeType: 'unknownConverted',
|
parentNodeType: 'unknownConverted',
|
||||||
slateNodes: slateNode.children,
|
slateNodes: slateNode.children!,
|
||||||
}),
|
}),
|
||||||
data: {
|
data: {
|
||||||
nodeData: slateNode,
|
nodeData: slateNode,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const SlateUnorderedListConverter: SlateNodeConverter = {
|
|||||||
canContainParagraphs: false,
|
canContainParagraphs: false,
|
||||||
converters,
|
converters,
|
||||||
parentNodeType: 'list',
|
parentNodeType: 'list',
|
||||||
slateNodes: slateNode.children,
|
slateNodes: slateNode.children!,
|
||||||
}),
|
}),
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
format: '',
|
format: '',
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export function convertSlateNodesToLexical({
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
const unknownConverter = converters.find((converter) => converter.nodeTypes.includes('unknown'))
|
const unknownConverter = converters.find((converter) => converter.nodeTypes.includes('unknown'))
|
||||||
|
// @ts-expect-error - vestiges of the migration to strict mode. Probably not important enough in this file to fix
|
||||||
return (
|
return (
|
||||||
slateNodes.map((slateNode, i) => {
|
slateNodes.map((slateNode, i) => {
|
||||||
if (!('type' in slateNode)) {
|
if (!('type' in slateNode)) {
|
||||||
@@ -66,7 +67,9 @@ export function convertSlateNodesToLexical({
|
|||||||
return convertParagraphNode(converters, slateNode)
|
return convertParagraphNode(converters, slateNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
const converter = converters.find((converter) => converter.nodeTypes.includes(slateNode.type))
|
const converter = converters.find((converter) =>
|
||||||
|
converter.nodeTypes.includes(slateNode.type!),
|
||||||
|
)
|
||||||
|
|
||||||
if (converter) {
|
if (converter) {
|
||||||
return converter.converter({ childIndex: i, converters, parentNodeType, slateNode })
|
return converter.converter({ childIndex: i, converters, parentNodeType, slateNode })
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export class UnknownConvertedNode extends DecoratorNode<JSX.Element> {
|
|||||||
return element
|
return element
|
||||||
}
|
}
|
||||||
|
|
||||||
decorate(): JSX.Element | null {
|
decorate(): JSX.Element {
|
||||||
return <Component data={this.__data} />
|
return <Component data={this.__data} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const Component: React.FC<Props> = (props) => {
|
|||||||
const relationshipElemRef = useRef<HTMLDivElement | null>(null)
|
const relationshipElemRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
const [editor] = useLexicalComposerContext()
|
const [editor] = useLexicalComposerContext()
|
||||||
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey)
|
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey!)
|
||||||
const { field } = useEditorConfigContext()
|
const { field } = useEditorConfigContext()
|
||||||
const {
|
const {
|
||||||
config: {
|
config: {
|
||||||
@@ -62,8 +62,8 @@ const Component: React.FC<Props> = (props) => {
|
|||||||
},
|
},
|
||||||
} = useConfig()
|
} = useConfig()
|
||||||
|
|
||||||
const [relatedCollection, setRelatedCollection] = useState(() =>
|
const [relatedCollection, setRelatedCollection] = useState(
|
||||||
collections.find((coll) => coll.slug === relationTo),
|
() => collections.find((coll) => coll.slug === relationTo)!,
|
||||||
)
|
)
|
||||||
|
|
||||||
const { i18n, t } = useTranslation()
|
const { i18n, t } = useTranslation()
|
||||||
@@ -80,7 +80,7 @@ const Component: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const removeRelationship = useCallback(() => {
|
const removeRelationship = useCallback(() => {
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
$getNodeByKey(nodeKey).remove()
|
$getNodeByKey(nodeKey!)?.remove()
|
||||||
})
|
})
|
||||||
}, [editor, nodeKey])
|
}, [editor, nodeKey])
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ const Component: React.FC<Props> = (props) => {
|
|||||||
if (isSelected && $isNodeSelection($getSelection())) {
|
if (isSelected && $isNodeSelection($getSelection())) {
|
||||||
const event: KeyboardEvent = payload
|
const event: KeyboardEvent = payload
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const node = $getNodeByKey(nodeKey)
|
const node = $getNodeByKey(nodeKey!)
|
||||||
if ($isRelationshipNode(node)) {
|
if ($isRelationshipNode(node)) {
|
||||||
node.remove()
|
node.remove()
|
||||||
return true
|
return true
|
||||||
@@ -172,9 +172,11 @@ const Component: React.FC<Props> = (props) => {
|
|||||||
el="div"
|
el="div"
|
||||||
icon="swap"
|
icon="swap"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
editor.dispatchCommand(INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND, {
|
if (nodeKey) {
|
||||||
replace: { nodeKey },
|
editor.dispatchCommand(INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND, {
|
||||||
})
|
replace: { nodeKey },
|
||||||
|
})
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
round
|
round
|
||||||
tooltip={t('fields:swapRelationship')}
|
tooltip={t('fields:swapRelationship')}
|
||||||
|
|||||||
@@ -44,12 +44,12 @@ type Props = {
|
|||||||
const RelationshipDrawerComponent: React.FC<Props> = ({ enabledCollectionSlugs }) => {
|
const RelationshipDrawerComponent: React.FC<Props> = ({ enabledCollectionSlugs }) => {
|
||||||
const [editor] = useLexicalComposerContext()
|
const [editor] = useLexicalComposerContext()
|
||||||
const [selectedCollectionSlug, setSelectedCollectionSlug] = useState(
|
const [selectedCollectionSlug, setSelectedCollectionSlug] = useState(
|
||||||
() => enabledCollectionSlugs[0],
|
() => enabledCollectionSlugs?.[0],
|
||||||
)
|
)
|
||||||
const [replaceNodeKey, setReplaceNodeKey] = useState<null | string>(null)
|
const [replaceNodeKey, setReplaceNodeKey] = useState<null | string>(null)
|
||||||
|
|
||||||
const [ListDrawer, ListDrawerToggler, { closeDrawer, isDrawerOpen, openDrawer }] = useListDrawer({
|
const [ListDrawer, ListDrawerToggler, { closeDrawer, isDrawerOpen, openDrawer }] = useListDrawer({
|
||||||
collectionSlugs: enabledCollectionSlugs,
|
collectionSlugs: enabledCollectionSlugs!,
|
||||||
selectedCollection: selectedCollectionSlug,
|
selectedCollection: selectedCollectionSlug,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -83,14 +83,14 @@ const RelationshipDrawerComponent: React.FC<Props> = ({ enabledCollectionSlugs }
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// always reset back to first option
|
// always reset back to first option
|
||||||
// TODO: this is not working, see the ListDrawer component
|
// TODO: this is not working, see the ListDrawer component
|
||||||
setSelectedCollectionSlug(enabledCollectionSlugs[0])
|
setSelectedCollectionSlug(enabledCollectionSlugs?.[0])
|
||||||
}, [isDrawerOpen, enabledCollectionSlugs])
|
}, [isDrawerOpen, enabledCollectionSlugs])
|
||||||
|
|
||||||
return <ListDrawer onSelect={onSelect} />
|
return <ListDrawer onSelect={onSelect} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RelationshipDrawer = (props: Props): React.ReactNode => {
|
export const RelationshipDrawer = (props: Props): React.ReactNode => {
|
||||||
return props?.enabledCollectionSlugs?.length > 0 ? ( // If enabledCollectionSlugs it overrides what EnabledRelationshipsCondition is doing
|
return (props?.enabledCollectionSlugs?.length ?? -1) > 0 ? ( // If enabledCollectionSlugs it overrides what EnabledRelationshipsCondition is doing
|
||||||
<RelationshipDrawerComponent {...props} />
|
<RelationshipDrawerComponent {...props} />
|
||||||
) : (
|
) : (
|
||||||
<EnabledRelationshipsCondition {...props}>
|
<EnabledRelationshipsCondition {...props}>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const RelationshipPlugin: PluginComponent<RelationshipFeatureProps> = ({
|
|||||||
config: { collections },
|
config: { collections },
|
||||||
} = useConfig()
|
} = useConfig()
|
||||||
|
|
||||||
let enabledRelations: string[] = null
|
let enabledRelations: null | string[] = null
|
||||||
|
|
||||||
if (clientProps?.enabledCollections) {
|
if (clientProps?.enabledCollections) {
|
||||||
enabledRelations = clientProps?.enabledCollections
|
enabledRelations = clientProps?.enabledCollections
|
||||||
@@ -65,7 +65,7 @@ export const RelationshipPlugin: PluginComponent<RelationshipFeatureProps> = ({
|
|||||||
$isParagraphNode(focusNode) &&
|
$isParagraphNode(focusNode) &&
|
||||||
focusNode.getTextContentSize() === 0 &&
|
focusNode.getTextContentSize() === 0 &&
|
||||||
focusNode
|
focusNode
|
||||||
.getParent()
|
.getParentOrThrow()
|
||||||
.getChildren()
|
.getChildren()
|
||||||
.filter((node) => $isParagraphNode(node)).length > 1
|
.filter((node) => $isParagraphNode(node)).length > 1
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type FilteredCollectionsT = (
|
|||||||
|
|
||||||
const filterRichTextCollections: FilteredCollectionsT = (collections, options) => {
|
const filterRichTextCollections: FilteredCollectionsT = (collections, options) => {
|
||||||
return collections.filter(({ slug, admin: { enableRichTextRelationship }, upload }) => {
|
return collections.filter(({ slug, admin: { enableRichTextRelationship }, upload }) => {
|
||||||
if (!options.visibleEntities.collections.includes(slug)) {
|
if (!options?.visibleEntities.collections.includes(slug)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export const EnabledRelationshipsCondition: React.FC<any> = (props) => {
|
|||||||
const { visibleEntities } = useEntityVisibility()
|
const { visibleEntities } = useEntityVisibility()
|
||||||
|
|
||||||
const [enabledCollectionSlugs] = React.useState(() =>
|
const [enabledCollectionSlugs] = React.useState(() =>
|
||||||
filterRichTextCollections(collections, { uploads, user, visibleEntities }).map(
|
filterRichTextCollections(collections, { uploads, user: user!, visibleEntities }).map(
|
||||||
({ slug }) => slug,
|
({ slug }) => slug,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export class RelationshipServerNode extends DecoratorBlockNode {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element {
|
decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element | null {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,13 @@ function ButtonGroupItem({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!item.ChildComponent) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolbarButton editor={editor} item={item} key={item.key}>
|
<ToolbarButton editor={editor} item={item} key={item.key}>
|
||||||
{item?.ChildComponent && <item.ChildComponent />}
|
{<item.ChildComponent />}
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -58,14 +62,14 @@ function ToolbarGroupComponent({
|
|||||||
const {
|
const {
|
||||||
field: { richTextComponentMap },
|
field: { richTextComponentMap },
|
||||||
} = useEditorConfigContext()
|
} = useEditorConfigContext()
|
||||||
const [dropdownLabel, setDropdownLabel] = React.useState<null | string>(null)
|
const [dropdownLabel, setDropdownLabel] = React.useState<string | undefined>(undefined)
|
||||||
const [DropdownIcon, setDropdownIcon] = React.useState<null | React.FC>(null)
|
const [DropdownIcon, setDropdownIcon] = React.useState<React.FC | undefined>(undefined)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (group?.type === 'dropdown' && group.items.length && group.ChildComponent) {
|
if (group?.type === 'dropdown' && group.items.length && group.ChildComponent) {
|
||||||
setDropdownIcon(() => group.ChildComponent)
|
setDropdownIcon(() => group.ChildComponent!)
|
||||||
} else {
|
} else {
|
||||||
setDropdownIcon(null)
|
setDropdownIcon(undefined)
|
||||||
}
|
}
|
||||||
}, [group])
|
}, [group])
|
||||||
|
|
||||||
@@ -73,11 +77,11 @@ function ToolbarGroupComponent({
|
|||||||
({ activeItems }: { activeItems: ToolbarGroupItem[] }) => {
|
({ activeItems }: { activeItems: ToolbarGroupItem[] }) => {
|
||||||
if (!activeItems.length) {
|
if (!activeItems.length) {
|
||||||
if (group?.type === 'dropdown' && group.items.length && group.ChildComponent) {
|
if (group?.type === 'dropdown' && group.items.length && group.ChildComponent) {
|
||||||
setDropdownIcon(() => group.ChildComponent)
|
setDropdownIcon(() => group.ChildComponent!)
|
||||||
setDropdownLabel(null)
|
setDropdownLabel(undefined)
|
||||||
} else {
|
} else {
|
||||||
setDropdownIcon(null)
|
setDropdownIcon(undefined)
|
||||||
setDropdownLabel(null)
|
setDropdownLabel(undefined)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -153,7 +157,7 @@ function FixedToolbar({
|
|||||||
}): React.ReactNode {
|
}): React.ReactNode {
|
||||||
const currentToolbarRef = React.useRef<HTMLDivElement>(null)
|
const currentToolbarRef = React.useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const { y } = useScrollInfo()
|
const { y } = useScrollInfo!()
|
||||||
|
|
||||||
// Memoize the parent toolbar element
|
// Memoize the parent toolbar element
|
||||||
const parentToolbarElem = useMemo(() => {
|
const parentToolbarElem = useMemo(() => {
|
||||||
@@ -192,13 +196,13 @@ function FixedToolbar({
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (overlapping) {
|
if (overlapping) {
|
||||||
currentToolbarRef.current.className = 'fixed-toolbar fixed-toolbar--overlapping'
|
currentToolbarElem.className = 'fixed-toolbar fixed-toolbar--overlapping'
|
||||||
parentToolbarElem.className = 'fixed-toolbar fixed-toolbar--hide'
|
parentToolbarElem.className = 'fixed-toolbar fixed-toolbar--hide'
|
||||||
} else {
|
} else {
|
||||||
if (!currentToolbarRef.current.classList.contains('fixed-toolbar--overlapping')) {
|
if (!currentToolbarElem.classList.contains('fixed-toolbar--overlapping')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
currentToolbarRef.current.className = 'fixed-toolbar'
|
currentToolbarElem.className = 'fixed-toolbar'
|
||||||
parentToolbarElem.className = 'fixed-toolbar'
|
parentToolbarElem.className = 'fixed-toolbar'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -40,10 +40,13 @@ function ButtonGroupItem({
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (!item.ChildComponent) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolbarButton editor={editor} item={item} key={item.key}>
|
<ToolbarButton editor={editor} item={item} key={item.key}>
|
||||||
{item?.ChildComponent && <item.ChildComponent />}
|
<item.ChildComponent />
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -61,13 +64,13 @@ function ToolbarGroupComponent({
|
|||||||
}): React.ReactNode {
|
}): React.ReactNode {
|
||||||
const { editorConfig } = useEditorConfigContext()
|
const { editorConfig } = useEditorConfigContext()
|
||||||
|
|
||||||
const [DropdownIcon, setDropdownIcon] = React.useState<null | React.FC>(null)
|
const [DropdownIcon, setDropdownIcon] = React.useState<React.FC | undefined>()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (group?.type === 'dropdown' && group.items.length && group.ChildComponent) {
|
if (group?.type === 'dropdown' && group.items.length && group.ChildComponent) {
|
||||||
setDropdownIcon(() => group.ChildComponent)
|
setDropdownIcon(() => group.ChildComponent)
|
||||||
} else {
|
} else {
|
||||||
setDropdownIcon(null)
|
setDropdownIcon(undefined)
|
||||||
}
|
}
|
||||||
}, [group])
|
}, [group])
|
||||||
|
|
||||||
@@ -77,7 +80,7 @@ function ToolbarGroupComponent({
|
|||||||
if (group?.type === 'dropdown' && group.items.length && group.ChildComponent) {
|
if (group?.type === 'dropdown' && group.items.length && group.ChildComponent) {
|
||||||
setDropdownIcon(() => group.ChildComponent)
|
setDropdownIcon(() => group.ChildComponent)
|
||||||
} else {
|
} else {
|
||||||
setDropdownIcon(null)
|
setDropdownIcon(undefined)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ export const ToolbarButton = ({
|
|||||||
const updateStates = useCallback(() => {
|
const updateStates = useCallback(() => {
|
||||||
editor.getEditorState().read(() => {
|
editor.getEditorState().read(() => {
|
||||||
const selection = $getSelection()
|
const selection = $getSelection()
|
||||||
|
if (!selection) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (item.isActive) {
|
if (item.isActive) {
|
||||||
const isActive = item.isActive({ editor, editorConfigContext, selection })
|
const isActive = item.isActive({ editor, editorConfigContext, selection })
|
||||||
if (active !== isActive) {
|
if (active !== isActive) {
|
||||||
@@ -85,7 +88,7 @@ export const ToolbarButton = ({
|
|||||||
|
|
||||||
editor.focus(() => {
|
editor.focus(() => {
|
||||||
// We need to wrap the onSelect in the callback, so the editor is properly focused before the onSelect is called.
|
// We need to wrap the onSelect in the callback, so the editor is properly focused before the onSelect is called.
|
||||||
item.onSelect({
|
item.onSelect?.({
|
||||||
editor,
|
editor,
|
||||||
isActive: active,
|
isActive: active,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ export function DropDownItem({
|
|||||||
|
|
||||||
editor.focus(() => {
|
editor.focus(() => {
|
||||||
// We need to wrap the onSelect in the callback, so the editor is properly focused before the onSelect is called.
|
// We need to wrap the onSelect in the callback, so the editor is properly focused before the onSelect is called.
|
||||||
item.onSelect({
|
item.onSelect?.({
|
||||||
editor,
|
editor,
|
||||||
isActive: active,
|
isActive: active!,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,9 @@ export const ToolbarDropdown = ({
|
|||||||
const updateStates = useCallback(() => {
|
const updateStates = useCallback(() => {
|
||||||
editor.getEditorState().read(() => {
|
editor.getEditorState().read(() => {
|
||||||
const selection = $getSelection()
|
const selection = $getSelection()
|
||||||
|
if (!selection) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const _activeItemKeys: string[] = []
|
const _activeItemKeys: string[] = []
|
||||||
const _activeItems: ToolbarGroupItem[] = []
|
const _activeItems: ToolbarGroupItem[] = []
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export type ToolbarGroupItem = {
|
|||||||
label?:
|
label?:
|
||||||
| ((args: {
|
| ((args: {
|
||||||
i18n: I18nClient<{}, string>
|
i18n: I18nClient<{}, string>
|
||||||
richTextComponentMap: Map<string, React.ReactNode>
|
richTextComponentMap?: Map<string, React.ReactNode>
|
||||||
}) => string)
|
}) => string)
|
||||||
| string
|
| string
|
||||||
/** Each toolbar item needs to have a unique key. */
|
/** Each toolbar item needs to have a unique key. */
|
||||||
|
|||||||
@@ -286,6 +286,7 @@ export type ServerFeature<ServerProps, ClientFeatureProps> = {
|
|||||||
* This determines what props will be available on the Client.
|
* This determines what props will be available on the Client.
|
||||||
*/
|
*/
|
||||||
clientFeatureProps?: ClientFeatureProps
|
clientFeatureProps?: ClientFeatureProps
|
||||||
|
// @ts-expect-error - TODO: fix this
|
||||||
componentImports?: Config['admin']['importMap']['generators'][0] | PayloadComponent[]
|
componentImports?: Config['admin']['importMap']['generators'][0] | PayloadComponent[]
|
||||||
componentMap?:
|
componentMap?:
|
||||||
| ((args: { i18n: I18nClient; payload: Payload; props: ServerProps; schemaPath: string }) => {
|
| ((args: { i18n: I18nClient; payload: Payload; props: ServerProps; schemaPath: string }) => {
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ const Component: React.FC<ElementProps> = (props) => {
|
|||||||
|
|
||||||
const { i18n, t } = useTranslation()
|
const { i18n, t } = useTranslation()
|
||||||
const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0)
|
const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0)
|
||||||
const [relatedCollection] = useState<ClientCollectionConfig>(() =>
|
const [relatedCollection] = useState<ClientCollectionConfig>(
|
||||||
collections.find((coll) => coll.slug === relationTo),
|
() => collections.find((coll) => coll.slug === relationTo)!,
|
||||||
)
|
)
|
||||||
|
|
||||||
const componentID = useId()
|
const componentID = useId()
|
||||||
@@ -107,7 +107,7 @@ const Component: React.FC<ElementProps> = (props) => {
|
|||||||
|
|
||||||
const removeUpload = useCallback(() => {
|
const removeUpload = useCallback(() => {
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
$getNodeByKey(nodeKey).remove()
|
$getNodeByKey(nodeKey)?.remove()
|
||||||
})
|
})
|
||||||
}, [editor, nodeKey])
|
}, [editor, nodeKey])
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const insertUpload = ({
|
|||||||
}) => {
|
}) => {
|
||||||
if (!replaceNodeKey) {
|
if (!replaceNodeKey) {
|
||||||
editor.dispatchCommand(INSERT_UPLOAD_COMMAND, {
|
editor.dispatchCommand(INSERT_UPLOAD_COMMAND, {
|
||||||
|
// @ts-expect-error - TODO: fix this
|
||||||
fields: null,
|
fields: null,
|
||||||
relationTo,
|
relationTo,
|
||||||
value,
|
value,
|
||||||
@@ -35,6 +36,7 @@ const insertUpload = ({
|
|||||||
node.replace(
|
node.replace(
|
||||||
$createUploadNode({
|
$createUploadNode({
|
||||||
data: {
|
data: {
|
||||||
|
// @ts-expect-error - TODO: fix this
|
||||||
fields: null,
|
fields: null,
|
||||||
relationTo,
|
relationTo,
|
||||||
value,
|
value,
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const UploadPlugin: PluginComponentWithAnchor<UploadFeaturePropsClient> =
|
|||||||
$isParagraphNode(focusNode) &&
|
$isParagraphNode(focusNode) &&
|
||||||
focusNode.getTextContentSize() === 0 &&
|
focusNode.getTextContentSize() === 0 &&
|
||||||
focusNode
|
focusNode
|
||||||
.getParent()
|
.getParentOrThrow()
|
||||||
.getChildren()
|
.getChildren()
|
||||||
.filter((node) => $isParagraphNode(node)).length > 1
|
.filter((node) => $isParagraphNode(node)).length > 1
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -109,8 +109,8 @@ export const UploadFeature = createServerFeature<
|
|||||||
req,
|
req,
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
}) => {
|
}) => {
|
||||||
// @ts-expect-error
|
// @ts-expect-error - for backwards-compatibility
|
||||||
const id = node?.value?.id || node?.value // for backwards-compatibility
|
const id = node?.value?.id || node?.value
|
||||||
|
|
||||||
if (req?.payload) {
|
if (req?.payload) {
|
||||||
const uploadDocument: {
|
const uploadDocument: {
|
||||||
@@ -141,7 +141,7 @@ export const UploadFeature = createServerFeature<
|
|||||||
return `<img />`
|
return `<img />`
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = getAbsoluteURL(uploadDocument?.value?.url, req?.payload)
|
const url = getAbsoluteURL(uploadDocument?.value?.url ?? '', req?.payload)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the upload is not an image, return a link to the upload
|
* If the upload is not an image, return a link to the upload
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const uploadValidation = (
|
|||||||
siblingData: node?.fields ?? {},
|
siblingData: node?.fields ?? {},
|
||||||
})
|
})
|
||||||
|
|
||||||
let errorPaths = []
|
let errorPaths: string[] = []
|
||||||
for (const fieldKey in result) {
|
for (const fieldKey in result) {
|
||||||
if (result[fieldKey].errorPaths) {
|
if (result[fieldKey].errorPaths) {
|
||||||
errorPaths = errorPaths.concat(result[fieldKey].errorPaths)
|
errorPaths = errorPaths.concat(result[fieldKey].errorPaths)
|
||||||
|
|||||||
@@ -34,13 +34,7 @@ const RichTextComponent: React.FC<
|
|||||||
field: {
|
field: {
|
||||||
name,
|
name,
|
||||||
_path: pathFromProps,
|
_path: pathFromProps,
|
||||||
admin: {
|
admin: { className, components, readOnly: readOnlyFromAdmin, style, width } = {},
|
||||||
className,
|
|
||||||
components: { Description, Error, Label },
|
|
||||||
readOnly: readOnlyFromAdmin,
|
|
||||||
style,
|
|
||||||
width,
|
|
||||||
} = {},
|
|
||||||
required,
|
required,
|
||||||
},
|
},
|
||||||
field,
|
field,
|
||||||
@@ -48,6 +42,9 @@ const RichTextComponent: React.FC<
|
|||||||
readOnly: readOnlyFromTopLevelProps,
|
readOnly: readOnlyFromTopLevelProps,
|
||||||
validate, // Users can pass in client side validation if they WANT to, but it's not required anymore
|
validate, // Users can pass in client side validation if they WANT to, but it's not required anymore
|
||||||
} = props
|
} = props
|
||||||
|
const Description = components?.Description
|
||||||
|
const Error = components?.Error
|
||||||
|
const Label = components?.Label
|
||||||
const readOnlyFromProps = readOnlyFromTopLevelProps || readOnlyFromAdmin
|
const readOnlyFromProps = readOnlyFromTopLevelProps || readOnlyFromAdmin
|
||||||
|
|
||||||
const memoizedValidate = useCallback(
|
const memoizedValidate = useCallback(
|
||||||
@@ -65,6 +62,7 @@ const RichTextComponent: React.FC<
|
|||||||
|
|
||||||
const fieldType = useField<SerializedEditorState>({
|
const fieldType = useField<SerializedEditorState>({
|
||||||
path: pathFromContext ?? pathFromProps ?? name,
|
path: pathFromContext ?? pathFromProps ?? name,
|
||||||
|
// @ts-expect-error: TODO: Fix this
|
||||||
validate: memoizedValidate,
|
validate: memoizedValidate,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -95,12 +93,12 @@ const RichTextComponent: React.FC<
|
|||||||
>
|
>
|
||||||
<FieldError
|
<FieldError
|
||||||
CustomError={Error}
|
CustomError={Error}
|
||||||
field={field}
|
|
||||||
path={path}
|
path={path}
|
||||||
{...(errorProps || {})}
|
{...(errorProps || {})}
|
||||||
alignCaret="left"
|
alignCaret="left"
|
||||||
|
field={field}
|
||||||
/>
|
/>
|
||||||
<FieldLabel field={field} Label={Label} {...(labelProps || {})} />
|
<FieldLabel Label={Label} {...(labelProps || {})} field={field} />
|
||||||
<div className={`${baseClass}__wrap`}>
|
<div className={`${baseClass}__wrap`}>
|
||||||
<ErrorBoundary fallbackRender={fallbackRender} onReset={() => {}}>
|
<ErrorBoundary fallbackRender={fallbackRender} onReset={() => {}}>
|
||||||
<LexicalProvider
|
<LexicalProvider
|
||||||
@@ -124,7 +122,7 @@ const RichTextComponent: React.FC<
|
|||||||
value={value}
|
value={value}
|
||||||
/>
|
/>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
<FieldDescription Description={Description} field={field} {...(descriptionProps || {})} />
|
<FieldDescription Description={Description} {...(descriptionProps || {})} field={field} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ export const RichTextField: React.FC<LexicalRichTextFieldProps> = (props) => {
|
|||||||
} = props
|
} = props
|
||||||
|
|
||||||
const [finalSanitizedEditorConfig, setFinalSanitizedEditorConfig] =
|
const [finalSanitizedEditorConfig, setFinalSanitizedEditorConfig] =
|
||||||
useState<SanitizedClientEditorConfig>(null)
|
useState<null | SanitizedClientEditorConfig>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (finalSanitizedEditorConfig) {
|
if (finalSanitizedEditorConfig) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const clientFeatures: GeneratedFeatureProviderComponent[] = richTextComponentMap.get(
|
const clientFeatures: GeneratedFeatureProviderComponent[] = richTextComponentMap?.get(
|
||||||
'features',
|
'features',
|
||||||
) as GeneratedFeatureProviderComponent[]
|
) as GeneratedFeatureProviderComponent[]
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import type { JSONSchema4 } from 'json-schema'
|
import type { JSONSchema4 } from 'json-schema'
|
||||||
import type {
|
import type { SerializedEditorState, SerializedLexicalNode } from 'lexical'
|
||||||
EditorConfig as LexicalEditorConfig,
|
|
||||||
SerializedEditorState,
|
|
||||||
SerializedLexicalNode,
|
|
||||||
} from 'lexical'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
afterChangeTraverseFields,
|
afterChangeTraverseFields,
|
||||||
@@ -38,7 +34,7 @@ import { getGenerateSchemaMap } from './utilities/generateSchemaMap.js'
|
|||||||
import { recurseNodeTree } from './utilities/recurseNodeTree.js'
|
import { recurseNodeTree } from './utilities/recurseNodeTree.js'
|
||||||
import { richTextValidateHOC } from './validate/index.js'
|
import { richTextValidateHOC } from './validate/index.js'
|
||||||
|
|
||||||
let defaultSanitizedServerEditorConfig: SanitizedServerEditorConfig = null
|
let defaultSanitizedServerEditorConfig: null | SanitizedServerEditorConfig = null
|
||||||
let checkedDependencies = false
|
let checkedDependencies = false
|
||||||
|
|
||||||
export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapterProvider {
|
export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapterProvider {
|
||||||
@@ -106,8 +102,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
features = deepCopyObject(defaultEditorFeatures)
|
features = deepCopyObject(defaultEditorFeatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
const lexical: LexicalEditorConfig =
|
const lexical = props.lexical ?? deepCopyObjectSimple(defaultEditorConfig.lexical)!
|
||||||
props.lexical ?? deepCopyObjectSimple(defaultEditorConfig.lexical)
|
|
||||||
|
|
||||||
resolvedFeatureMap = await loadFeatures({
|
resolvedFeatureMap = await loadFeatures({
|
||||||
config,
|
config,
|
||||||
@@ -212,8 +207,8 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!finalSanitizedEditorConfig.features.nodeHooks.afterChange.size &&
|
!finalSanitizedEditorConfig.features.nodeHooks?.afterChange?.size &&
|
||||||
!finalSanitizedEditorConfig.features.getSubFields.size
|
!finalSanitizedEditorConfig.features.getSubFields?.size
|
||||||
) {
|
) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@@ -240,9 +235,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
|
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
for (let [id, node] of Object.entries(nodeIDMap)) {
|
for (let [id, node] of Object.entries(nodeIDMap)) {
|
||||||
const afterChangeHooks = finalSanitizedEditorConfig.features.nodeHooks.afterChange
|
const afterChangeHooks = finalSanitizedEditorConfig.features.nodeHooks?.afterChange
|
||||||
if (afterChangeHooks?.has(node.type)) {
|
const afterChangeHooksForNode = afterChangeHooks?.get(node.type)
|
||||||
for (const hook of afterChangeHooks.get(node.type)) {
|
if (afterChangeHooksForNode) {
|
||||||
|
for (const hook of afterChangeHooksForNode) {
|
||||||
if (!originalNodeIDMap[id]) {
|
if (!originalNodeIDMap[id]) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'(afterChange) No original node found for node with id',
|
'(afterChange) No original node found for node with id',
|
||||||
@@ -265,12 +261,12 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const subFieldFn = finalSanitizedEditorConfig.features.getSubFields.get(node.type)
|
const subFieldFn = finalSanitizedEditorConfig.features.getSubFields?.get(node.type)
|
||||||
const subFieldDataFn = finalSanitizedEditorConfig.features.getSubFieldsData.get(
|
const subFieldDataFn = finalSanitizedEditorConfig.features.getSubFieldsData?.get(
|
||||||
node.type,
|
node.type,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (subFieldFn) {
|
if (subFieldFn && subFieldDataFn) {
|
||||||
const subFields = subFieldFn({ node, req })
|
const subFields = subFieldFn({ node, req })
|
||||||
const data = subFieldDataFn({ node, req }) ?? {}
|
const data = subFieldDataFn({ node, req }) ?? {}
|
||||||
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
|
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
|
||||||
@@ -333,8 +329,8 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!finalSanitizedEditorConfig.features.nodeHooks.afterRead.size &&
|
!finalSanitizedEditorConfig.features.nodeHooks?.afterRead?.size &&
|
||||||
!finalSanitizedEditorConfig.features.getSubFields.size
|
!finalSanitizedEditorConfig.features.getSubFields?.size
|
||||||
) {
|
) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@@ -346,37 +342,38 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
})
|
})
|
||||||
|
|
||||||
for (let node of flattenedNodes) {
|
for (let node of flattenedNodes) {
|
||||||
const afterReadHooks = finalSanitizedEditorConfig.features.nodeHooks.afterRead
|
const afterReadHooks = finalSanitizedEditorConfig.features.nodeHooks?.afterRead
|
||||||
if (afterReadHooks?.has(node.type)) {
|
const afterReadHooksForNode = afterReadHooks?.get(node.type)
|
||||||
for (const hook of afterReadHooks.get(node.type)) {
|
if (afterReadHooksForNode) {
|
||||||
|
for (const hook of afterReadHooksForNode) {
|
||||||
node = await hook({
|
node = await hook({
|
||||||
context,
|
context,
|
||||||
currentDepth,
|
currentDepth: currentDepth!,
|
||||||
depth,
|
depth: depth!,
|
||||||
draft,
|
draft: draft!,
|
||||||
fallbackLocale,
|
fallbackLocale: fallbackLocale!,
|
||||||
fieldPromises,
|
fieldPromises: fieldPromises!,
|
||||||
findMany,
|
findMany: findMany!,
|
||||||
flattenLocales,
|
flattenLocales: flattenLocales!,
|
||||||
locale,
|
locale: locale!,
|
||||||
node,
|
node,
|
||||||
overrideAccess,
|
overrideAccess: overrideAccess!,
|
||||||
parentRichTextFieldPath: path,
|
parentRichTextFieldPath: path,
|
||||||
parentRichTextFieldSchemaPath: schemaPath,
|
parentRichTextFieldSchemaPath: schemaPath,
|
||||||
populationPromises,
|
populationPromises: populationPromises!,
|
||||||
req,
|
req,
|
||||||
showHiddenFields,
|
showHiddenFields: showHiddenFields!,
|
||||||
triggerAccessControl,
|
triggerAccessControl: triggerAccessControl!,
|
||||||
triggerHooks,
|
triggerHooks: triggerHooks!,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const subFieldFn = finalSanitizedEditorConfig.features.getSubFields.get(node.type)
|
const subFieldFn = finalSanitizedEditorConfig.features.getSubFields?.get(node.type)
|
||||||
const subFieldDataFn = finalSanitizedEditorConfig.features.getSubFieldsData.get(
|
const subFieldDataFn = finalSanitizedEditorConfig.features.getSubFieldsData?.get(
|
||||||
node.type,
|
node.type,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (subFieldFn) {
|
if (subFieldFn && subFieldDataFn) {
|
||||||
const subFields = subFieldFn({ node, req })
|
const subFields = subFieldFn({ node, req })
|
||||||
const data = subFieldDataFn({ node, req }) ?? {}
|
const data = subFieldDataFn({ node, req }) ?? {}
|
||||||
|
|
||||||
@@ -384,23 +381,23 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
afterReadTraverseFields({
|
afterReadTraverseFields({
|
||||||
collection,
|
collection,
|
||||||
context,
|
context,
|
||||||
currentDepth,
|
currentDepth: currentDepth!,
|
||||||
depth,
|
depth: depth!,
|
||||||
doc: data,
|
doc: data,
|
||||||
draft,
|
draft: draft!,
|
||||||
fallbackLocale,
|
fallbackLocale: fallbackLocale!,
|
||||||
fieldPromises,
|
fieldPromises: fieldPromises!,
|
||||||
fields: subFields,
|
fields: subFields,
|
||||||
findMany,
|
findMany: findMany!,
|
||||||
flattenLocales,
|
flattenLocales: flattenLocales!,
|
||||||
global,
|
global,
|
||||||
locale,
|
locale: locale!,
|
||||||
overrideAccess,
|
overrideAccess: overrideAccess!,
|
||||||
path,
|
path,
|
||||||
populationPromises,
|
populationPromises: populationPromises!,
|
||||||
req,
|
req,
|
||||||
schemaPath,
|
schemaPath,
|
||||||
showHiddenFields,
|
showHiddenFields: showHiddenFields!,
|
||||||
siblingDoc: data,
|
siblingDoc: data,
|
||||||
triggerAccessControl,
|
triggerAccessControl,
|
||||||
triggerHooks,
|
triggerHooks,
|
||||||
@@ -438,8 +435,8 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!finalSanitizedEditorConfig.features.nodeHooks.beforeChange.size &&
|
!finalSanitizedEditorConfig.features.nodeHooks?.beforeChange?.size &&
|
||||||
!finalSanitizedEditorConfig.features.getSubFields.size
|
!finalSanitizedEditorConfig.features.getSubFields?.size
|
||||||
) {
|
) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@@ -469,7 +466,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
nodes: (value as SerializedEditorState)?.root?.children ?? [],
|
nodes: (value as SerializedEditorState)?.root?.children ?? [],
|
||||||
})
|
})
|
||||||
|
|
||||||
if (siblingDocWithLocales?.[field.name]) {
|
if (field.name && siblingDocWithLocales?.[field.name]) {
|
||||||
recurseNodeTree({
|
recurseNodeTree({
|
||||||
nodeIDMap: originalNodeWithLocalesIDMap,
|
nodeIDMap: originalNodeWithLocalesIDMap,
|
||||||
nodes:
|
nodes:
|
||||||
@@ -480,9 +477,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
|
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
for (let [id, node] of Object.entries(nodeIDMap)) {
|
for (let [id, node] of Object.entries(nodeIDMap)) {
|
||||||
const beforeChangeHooks = finalSanitizedEditorConfig.features.nodeHooks.beforeChange
|
const beforeChangeHooks = finalSanitizedEditorConfig.features.nodeHooks?.beforeChange
|
||||||
if (beforeChangeHooks?.has(node.type)) {
|
const beforeChangeHooksForNode = beforeChangeHooks?.get(node.type)
|
||||||
for (const hook of beforeChangeHooks.get(node.type)) {
|
if (beforeChangeHooksForNode) {
|
||||||
|
for (const hook of beforeChangeHooksForNode) {
|
||||||
if (!originalNodeIDMap[id]) {
|
if (!originalNodeIDMap[id]) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'(beforeChange) No original node found for node with id',
|
'(beforeChange) No original node found for node with id',
|
||||||
@@ -496,26 +494,26 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
}
|
}
|
||||||
node = await hook({
|
node = await hook({
|
||||||
context,
|
context,
|
||||||
errors,
|
errors: errors!,
|
||||||
mergeLocaleActions,
|
mergeLocaleActions: mergeLocaleActions!,
|
||||||
node,
|
node,
|
||||||
operation,
|
operation: operation!,
|
||||||
originalNode: originalNodeIDMap[id],
|
originalNode: originalNodeIDMap[id],
|
||||||
originalNodeWithLocales: originalNodeWithLocalesIDMap[id],
|
originalNodeWithLocales: originalNodeWithLocalesIDMap[id],
|
||||||
parentRichTextFieldPath: path,
|
parentRichTextFieldPath: path,
|
||||||
parentRichTextFieldSchemaPath: schemaPath,
|
parentRichTextFieldSchemaPath: schemaPath,
|
||||||
req,
|
req,
|
||||||
skipValidation,
|
skipValidation: skipValidation!,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const subFieldFn = finalSanitizedEditorConfig.features.getSubFields.get(node.type)
|
const subFieldFn = finalSanitizedEditorConfig.features.getSubFields?.get(node.type)
|
||||||
const subFieldDataFn = finalSanitizedEditorConfig.features.getSubFieldsData.get(
|
const subFieldDataFn = finalSanitizedEditorConfig.features.getSubFieldsData?.get(
|
||||||
node.type,
|
node.type,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (subFieldFn) {
|
if (subFieldFn && subFieldDataFn) {
|
||||||
const subFields = subFieldFn({ node, req })
|
const subFields = subFieldFn({ node, req })
|
||||||
const data = subFieldDataFn({ node, req }) ?? {}
|
const data = subFieldDataFn({ node, req }) ?? {}
|
||||||
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
|
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
|
||||||
@@ -533,11 +531,11 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
data,
|
data,
|
||||||
doc: originalData,
|
doc: originalData,
|
||||||
docWithLocales: originalDataWithLocales ?? {},
|
docWithLocales: originalDataWithLocales ?? {},
|
||||||
errors,
|
errors: errors!,
|
||||||
fields: subFields,
|
fields: subFields,
|
||||||
global,
|
global,
|
||||||
mergeLocaleActions,
|
mergeLocaleActions: mergeLocaleActions!,
|
||||||
operation,
|
operation: operation!,
|
||||||
path,
|
path,
|
||||||
req,
|
req,
|
||||||
schemaPath,
|
schemaPath,
|
||||||
@@ -564,7 +562,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
[key: string]: SerializedLexicalNode
|
[key: string]: SerializedLexicalNode
|
||||||
} = {}
|
} = {}
|
||||||
|
|
||||||
const previousValue = siblingData[field.name]
|
const previousValue = siblingData[field.name!]
|
||||||
|
|
||||||
recurseNodeTree({
|
recurseNodeTree({
|
||||||
nodeIDMap: newOriginalNodeIDMap,
|
nodeIDMap: newOriginalNodeIDMap,
|
||||||
@@ -607,10 +605,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
|
|
||||||
// return value if there are NO hooks
|
// return value if there are NO hooks
|
||||||
if (
|
if (
|
||||||
!finalSanitizedEditorConfig.features.nodeHooks.beforeValidate.size &&
|
!finalSanitizedEditorConfig.features.nodeHooks?.beforeValidate?.size &&
|
||||||
!finalSanitizedEditorConfig.features.nodeHooks.afterChange.size &&
|
!finalSanitizedEditorConfig.features.nodeHooks?.afterChange?.size &&
|
||||||
!finalSanitizedEditorConfig.features.nodeHooks.beforeChange.size &&
|
!finalSanitizedEditorConfig.features.nodeHooks?.beforeChange?.size &&
|
||||||
!finalSanitizedEditorConfig.features.getSubFields.size
|
!finalSanitizedEditorConfig.features.getSubFields?.size
|
||||||
) {
|
) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@@ -662,7 +660,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
/**
|
/**
|
||||||
* Now that the maps for all hooks are set up, we can run the validate hook
|
* Now that the maps for all hooks are set up, we can run the validate hook
|
||||||
*/
|
*/
|
||||||
if (!finalSanitizedEditorConfig.features.nodeHooks.beforeValidate.size) {
|
if (!finalSanitizedEditorConfig.features.nodeHooks?.beforeValidate?.size) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
const nodeIDMap: {
|
const nodeIDMap: {
|
||||||
@@ -678,8 +676,9 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
for (let [id, node] of Object.entries(nodeIDMap)) {
|
for (let [id, node] of Object.entries(nodeIDMap)) {
|
||||||
const beforeValidateHooks =
|
const beforeValidateHooks =
|
||||||
finalSanitizedEditorConfig.features.nodeHooks.beforeValidate
|
finalSanitizedEditorConfig.features.nodeHooks.beforeValidate
|
||||||
if (beforeValidateHooks?.has(node.type)) {
|
const beforeValidateHooksForNode = beforeValidateHooks?.get(node.type)
|
||||||
for (const hook of beforeValidateHooks.get(node.type)) {
|
if (beforeValidateHooksForNode) {
|
||||||
|
for (const hook of beforeValidateHooksForNode) {
|
||||||
if (!originalNodeIDMap[id]) {
|
if (!originalNodeIDMap[id]) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'(beforeValidate) No original node found for node with id',
|
'(beforeValidate) No original node found for node with id',
|
||||||
@@ -696,19 +695,19 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
node,
|
node,
|
||||||
operation,
|
operation,
|
||||||
originalNode: originalNodeIDMap[id],
|
originalNode: originalNodeIDMap[id],
|
||||||
overrideAccess,
|
overrideAccess: overrideAccess!,
|
||||||
parentRichTextFieldPath: path,
|
parentRichTextFieldPath: path,
|
||||||
parentRichTextFieldSchemaPath: schemaPath,
|
parentRichTextFieldSchemaPath: schemaPath,
|
||||||
req,
|
req,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const subFieldFn = finalSanitizedEditorConfig.features.getSubFields.get(node.type)
|
const subFieldFn = finalSanitizedEditorConfig.features.getSubFields?.get(node.type)
|
||||||
const subFieldDataFn = finalSanitizedEditorConfig.features.getSubFieldsData.get(
|
const subFieldDataFn = finalSanitizedEditorConfig.features.getSubFieldsData?.get(
|
||||||
node.type,
|
node.type,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (subFieldFn) {
|
if (subFieldFn && subFieldDataFn) {
|
||||||
const subFields = subFieldFn({ node, req })
|
const subFields = subFieldFn({ node, req })
|
||||||
const data = subFieldDataFn({ node, req }) ?? {}
|
const data = subFieldDataFn({ node, req }) ?? {}
|
||||||
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
|
const originalData = subFieldDataFn({ node: originalNodeIDMap[id], req }) ?? {}
|
||||||
@@ -723,7 +722,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
|||||||
fields: subFields,
|
fields: subFields,
|
||||||
global,
|
global,
|
||||||
operation,
|
operation,
|
||||||
overrideAccess,
|
overrideAccess: overrideAccess!,
|
||||||
path,
|
path,
|
||||||
req,
|
req,
|
||||||
schemaPath,
|
schemaPath,
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ export const EditorPlugin: React.FC<{
|
|||||||
clientProps: unknown
|
clientProps: unknown
|
||||||
plugin: SanitizedPlugin
|
plugin: SanitizedPlugin
|
||||||
}> = ({ anchorElem, clientProps, plugin }) => {
|
}> = ({ anchorElem, clientProps, plugin }) => {
|
||||||
if (plugin.position === 'floatingAnchorElem') {
|
if (plugin.position === 'floatingAnchorElem' && anchorElem) {
|
||||||
return (
|
return (
|
||||||
plugin.Component && <plugin.Component anchorElem={anchorElem} clientProps={clientProps} />
|
plugin.Component && <plugin.Component anchorElem={anchorElem} clientProps={clientProps} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error - ts is not able to infer that plugin.Component is of type PluginComponent
|
||||||
return plugin.Component && <plugin.Component clientProps={clientProps} />
|
return plugin.Component && <plugin.Component clientProps={clientProps} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { LexicalContentEditable } from './ui/ContentEditable.js'
|
|||||||
|
|
||||||
export const LexicalEditor: React.FC<
|
export const LexicalEditor: React.FC<
|
||||||
{
|
{
|
||||||
editorContainerRef: React.RefObject<HTMLDivElement>
|
editorContainerRef: React.RefObject<HTMLDivElement | null>
|
||||||
} & Pick<LexicalProviderProps, 'editorConfig' | 'onChange'>
|
} & Pick<LexicalProviderProps, 'editorConfig' | 'onChange'>
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
const { editorConfig, editorContainerRef, onChange } = props
|
const { editorConfig, editorContainerRef, onChange } = props
|
||||||
@@ -98,13 +98,13 @@ export const LexicalEditor: React.FC<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{editorConfig.features.plugins.map((plugin) => {
|
{editorConfig.features.plugins?.map((plugin) => {
|
||||||
if (plugin.position === 'aboveContainer') {
|
if (plugin.position === 'aboveContainer') {
|
||||||
return <EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
|
return <EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
<div className="editor-container" ref={editorContainerRef}>
|
<div className="editor-container" ref={editorContainerRef}>
|
||||||
{editorConfig.features.plugins.map((plugin) => {
|
{editorConfig.features.plugins?.map((plugin) => {
|
||||||
if (plugin.position === 'top') {
|
if (plugin.position === 'top') {
|
||||||
return (
|
return (
|
||||||
<EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
|
<EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
|
||||||
@@ -143,7 +143,7 @@ export const LexicalEditor: React.FC<
|
|||||||
<AddBlockHandlePlugin anchorElem={floatingAnchorElem} />
|
<AddBlockHandlePlugin anchorElem={floatingAnchorElem} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
{editorConfig.features.plugins.map((plugin) => {
|
{editorConfig.features.plugins?.map((plugin) => {
|
||||||
if (
|
if (
|
||||||
plugin.position === 'floatingAnchorElem' &&
|
plugin.position === 'floatingAnchorElem' &&
|
||||||
!(plugin.desktopOnly === true && isSmallWidthViewport)
|
!(plugin.desktopOnly === true && isSmallWidthViewport)
|
||||||
@@ -173,14 +173,14 @@ export const LexicalEditor: React.FC<
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<TabIndentationPlugin />
|
<TabIndentationPlugin />
|
||||||
{editorConfig.features.plugins.map((plugin) => {
|
{editorConfig.features.plugins?.map((plugin) => {
|
||||||
if (plugin.position === 'normal') {
|
if (plugin.position === 'normal') {
|
||||||
return (
|
return (
|
||||||
<EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
|
<EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
{editorConfig.features.plugins.map((plugin) => {
|
{editorConfig.features.plugins?.map((plugin) => {
|
||||||
if (plugin.position === 'bottom') {
|
if (plugin.position === 'bottom') {
|
||||||
return (
|
return (
|
||||||
<EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
|
<EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
|
||||||
@@ -188,7 +188,7 @@ export const LexicalEditor: React.FC<
|
|||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{editorConfig.features.plugins.map((plugin) => {
|
{editorConfig.features.plugins?.map((plugin) => {
|
||||||
if (plugin.position === 'belowContainer') {
|
if (plugin.position === 'belowContainer') {
|
||||||
return <EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
|
return <EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export interface EditorConfigContextType {
|
|||||||
uuid: string
|
uuid: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error: TODO: Fix this
|
||||||
const Context: React.Context<EditorConfigContextType> = createContext({
|
const Context: React.Context<EditorConfigContextType> = createContext({
|
||||||
editorConfig: null,
|
editorConfig: null,
|
||||||
field: null,
|
field: null,
|
||||||
@@ -46,7 +47,7 @@ export const EditorConfigProvider = ({
|
|||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
editorConfig: SanitizedClientEditorConfig
|
editorConfig: SanitizedClientEditorConfig
|
||||||
editorContainerRef: React.RefObject<HTMLDivElement>
|
editorContainerRef: React.RefObject<HTMLDivElement | null>
|
||||||
field: LexicalRichTextFieldProps['field']
|
field: LexicalRichTextFieldProps['field']
|
||||||
parentContext?: EditorConfigContextType
|
parentContext?: EditorConfigContextType
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
@@ -87,7 +88,7 @@ export const EditorConfigProvider = ({
|
|||||||
if (parentContext?.uuid) {
|
if (parentContext?.uuid) {
|
||||||
parentContext.focusEditor(editorContext)
|
parentContext.focusEditor(editorContext)
|
||||||
}
|
}
|
||||||
childrenEditors.current.forEach((childEditor, childUUID) => {
|
childrenEditors.current.forEach((childEditor) => {
|
||||||
childEditor.focusEditor(editorContext)
|
childEditor.focusEditor(editorContext)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export const sanitizeClientFeatures = (
|
|||||||
}
|
}
|
||||||
if (feature.plugins?.length) {
|
if (feature.plugins?.length) {
|
||||||
feature.plugins.forEach((plugin, i) => {
|
feature.plugins.forEach((plugin, i) => {
|
||||||
sanitized.plugins.push({
|
sanitized.plugins?.push({
|
||||||
clientProps: feature.sanitizedClientFeatureProps,
|
clientProps: feature.sanitizedClientFeatureProps,
|
||||||
Component: plugin.Component,
|
Component: plugin.Component,
|
||||||
key: feature.key + i,
|
key: feature.key + i,
|
||||||
@@ -219,7 +219,7 @@ export function sanitizeClientEditorConfig(
|
|||||||
return {
|
return {
|
||||||
admin,
|
admin,
|
||||||
features: sanitizeClientFeatures(resolvedClientFeatureMap),
|
features: sanitizeClientFeatures(resolvedClientFeatureMap),
|
||||||
lexical,
|
lexical: lexical!,
|
||||||
resolvedFeatureMap: resolvedClientFeatureMap,
|
resolvedFeatureMap: resolvedClientFeatureMap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,9 +188,9 @@ export async function loadFeatures({
|
|||||||
: featureProvider.feature
|
: featureProvider.feature
|
||||||
resolvedFeatures.set(featureProvider.key, {
|
resolvedFeatures.set(featureProvider.key, {
|
||||||
...feature,
|
...feature,
|
||||||
dependencies: featureProvider.dependencies,
|
dependencies: featureProvider.dependencies!,
|
||||||
dependenciesPriority: featureProvider.dependenciesPriority,
|
dependenciesPriority: featureProvider.dependenciesPriority!,
|
||||||
dependenciesSoft: featureProvider.dependenciesSoft,
|
dependenciesSoft: featureProvider.dependenciesSoft!,
|
||||||
key: featureProvider.key,
|
key: featureProvider.key,
|
||||||
order: loaded,
|
order: loaded,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -51,18 +51,20 @@ export const sanitizeServerFeatures = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (feature?.hooks?.beforeValidate?.length) {
|
if (feature?.hooks?.beforeValidate?.length) {
|
||||||
sanitized.hooks.beforeValidate = sanitized.hooks.beforeValidate.concat(
|
sanitized.hooks.beforeValidate = sanitized.hooks.beforeValidate?.concat(
|
||||||
feature.hooks.beforeValidate,
|
feature.hooks.beforeValidate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (feature?.hooks?.beforeChange?.length) {
|
if (feature?.hooks?.beforeChange?.length) {
|
||||||
sanitized.hooks.beforeChange = sanitized.hooks.beforeChange.concat(feature.hooks.beforeChange)
|
sanitized.hooks.beforeChange = sanitized.hooks.beforeChange?.concat(
|
||||||
|
feature.hooks.beforeChange,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (feature?.hooks?.afterRead?.length) {
|
if (feature?.hooks?.afterRead?.length) {
|
||||||
sanitized.hooks.afterRead = sanitized.hooks.afterRead.concat(feature.hooks.afterRead)
|
sanitized.hooks.afterRead = sanitized.hooks.afterRead?.concat(feature.hooks.afterRead)
|
||||||
}
|
}
|
||||||
if (feature?.hooks?.afterChange?.length) {
|
if (feature?.hooks?.afterChange?.length) {
|
||||||
sanitized.hooks.afterChange = sanitized.hooks.afterChange.concat(feature.hooks.afterChange)
|
sanitized.hooks.afterChange = sanitized.hooks.afterChange?.concat(feature.hooks.afterChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (feature?.i18n) {
|
if (feature?.i18n) {
|
||||||
@@ -90,22 +92,22 @@ export const sanitizeServerFeatures = (
|
|||||||
sanitized.converters.html.push(node.converters.html)
|
sanitized.converters.html.push(node.converters.html)
|
||||||
}
|
}
|
||||||
if (node?.hooks?.afterChange) {
|
if (node?.hooks?.afterChange) {
|
||||||
sanitized.nodeHooks.afterChange.set(nodeType, node.hooks.afterChange)
|
sanitized.nodeHooks?.afterChange?.set(nodeType, node.hooks.afterChange)
|
||||||
}
|
}
|
||||||
if (node?.hooks?.afterRead) {
|
if (node?.hooks?.afterRead) {
|
||||||
sanitized.nodeHooks.afterRead.set(nodeType, node.hooks.afterRead)
|
sanitized.nodeHooks?.afterRead?.set(nodeType, node.hooks.afterRead)
|
||||||
}
|
}
|
||||||
if (node?.hooks?.beforeChange) {
|
if (node?.hooks?.beforeChange) {
|
||||||
sanitized.nodeHooks.beforeChange.set(nodeType, node.hooks.beforeChange)
|
sanitized.nodeHooks?.beforeChange?.set(nodeType, node.hooks.beforeChange)
|
||||||
}
|
}
|
||||||
if (node?.hooks?.beforeValidate) {
|
if (node?.hooks?.beforeValidate) {
|
||||||
sanitized.nodeHooks.beforeValidate.set(nodeType, node.hooks.beforeValidate)
|
sanitized.nodeHooks?.beforeValidate?.set(nodeType, node.hooks.beforeValidate)
|
||||||
}
|
}
|
||||||
if (node?.getSubFields) {
|
if (node?.getSubFields) {
|
||||||
sanitized.getSubFields.set(nodeType, node.getSubFields)
|
sanitized.getSubFields?.set(nodeType, node.getSubFields)
|
||||||
}
|
}
|
||||||
if (node?.getSubFieldsData) {
|
if (node?.getSubFieldsData) {
|
||||||
sanitized.getSubFieldsData.set(nodeType, node.getSubFieldsData)
|
sanitized.getSubFieldsData?.set(nodeType, node.getSubFieldsData)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -129,13 +131,13 @@ export async function sanitizeServerEditorConfig(
|
|||||||
): Promise<SanitizedServerEditorConfig> {
|
): Promise<SanitizedServerEditorConfig> {
|
||||||
const resolvedFeatureMap = await loadFeatures({
|
const resolvedFeatureMap = await loadFeatures({
|
||||||
config,
|
config,
|
||||||
parentIsLocalized,
|
parentIsLocalized: parentIsLocalized!,
|
||||||
unSanitizedEditorConfig: editorConfig,
|
unSanitizedEditorConfig: editorConfig,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
features: sanitizeServerFeatures(resolvedFeatureMap),
|
features: sanitizeServerFeatures(resolvedFeatureMap),
|
||||||
lexical: editorConfig.lexical,
|
lexical: editorConfig.lexical!,
|
||||||
resolvedFeatureMap,
|
resolvedFeatureMap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ function tryToPositionRange(leadOffset: number, range: Range, editorWindow: Wind
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
function getQueryTextForSearch(editor: LexicalEditor): null | string {
|
function getQueryTextForSearch(editor: LexicalEditor): string | undefined {
|
||||||
let text = null
|
let text
|
||||||
editor.getEditorState().read(() => {
|
editor.getEditorState().read(() => {
|
||||||
const selection = $getSelection()
|
const selection = $getSelection()
|
||||||
if (!$isRangeSelection(selection)) {
|
if (!$isRangeSelection(selection)) {
|
||||||
@@ -207,7 +207,7 @@ export function LexicalTypeaheadMenuPlugin({
|
|||||||
if (
|
if (
|
||||||
!$isRangeSelection(selection) ||
|
!$isRangeSelection(selection) ||
|
||||||
!selection.isCollapsed() ||
|
!selection.isCollapsed() ||
|
||||||
text === null ||
|
text === undefined ||
|
||||||
range === null
|
range === null
|
||||||
) {
|
) {
|
||||||
closeTypeahead()
|
closeTypeahead()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export type SlashMenuItem = {
|
|||||||
label?:
|
label?:
|
||||||
| ((args: {
|
| ((args: {
|
||||||
i18n: I18nClient<{}, string>
|
i18n: I18nClient<{}, string>
|
||||||
richTextComponentMap: Map<string, React.ReactNode>
|
richTextComponentMap?: Map<string, React.ReactNode>
|
||||||
schemaPath: string
|
schemaPath: string
|
||||||
}) => string)
|
}) => string)
|
||||||
| string
|
| string
|
||||||
|
|||||||
@@ -101,11 +101,13 @@ export function SlashMenuPlugin({
|
|||||||
let groupWithItems: Array<SlashMenuGroup> = []
|
let groupWithItems: Array<SlashMenuGroup> = []
|
||||||
|
|
||||||
for (const dynamicItem of editorConfig.features.slashMenu.dynamicGroups) {
|
for (const dynamicItem of editorConfig.features.slashMenu.dynamicGroups) {
|
||||||
const dynamicGroupWithItems = dynamicItem({
|
if (queryString) {
|
||||||
editor,
|
const dynamicGroupWithItems = dynamicItem({
|
||||||
queryString,
|
editor,
|
||||||
})
|
queryString,
|
||||||
groupWithItems = groupWithItems.concat(dynamicGroupWithItems)
|
})
|
||||||
|
groupWithItems = groupWithItems.concat(dynamicGroupWithItems)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupWithItems
|
return groupWithItems
|
||||||
@@ -119,6 +121,7 @@ export function SlashMenuPlugin({
|
|||||||
|
|
||||||
if (queryString) {
|
if (queryString) {
|
||||||
// Filter current groups first
|
// Filter current groups first
|
||||||
|
// @ts-expect-error - TODO: fix this
|
||||||
groupsWithItems = groupsWithItems.map((group) => {
|
groupsWithItems = groupsWithItems.map((group) => {
|
||||||
const filteredItems = group.items.filter((item) => {
|
const filteredItems = group.items.filter((item) => {
|
||||||
let itemTitle = item.key
|
let itemTitle = item.key
|
||||||
@@ -207,7 +210,7 @@ export function SlashMenuPlugin({
|
|||||||
<div className={baseClass}>
|
<div className={baseClass}>
|
||||||
{groups.map((group) => {
|
{groups.map((group) => {
|
||||||
let groupTitle = group.key
|
let groupTitle = group.key
|
||||||
if (group.label) {
|
if (group.label && richTextComponentMap) {
|
||||||
groupTitle =
|
groupTitle =
|
||||||
typeof group.label === 'function'
|
typeof group.label === 'function'
|
||||||
? group.label({ i18n, richTextComponentMap, schemaPath })
|
? group.label({ i18n, richTextComponentMap, schemaPath })
|
||||||
|
|||||||
@@ -93,7 +93,10 @@ function useAddBlockHandle(
|
|||||||
if (!_emptyBlockElem) {
|
if (!_emptyBlockElem) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (hoveredElement?.node !== blockNode || hoveredElement?.elem !== _emptyBlockElem) {
|
if (
|
||||||
|
blockNode &&
|
||||||
|
(hoveredElement?.node !== blockNode || hoveredElement?.elem !== _emptyBlockElem)
|
||||||
|
) {
|
||||||
setHoveredElement({
|
setHoveredElement({
|
||||||
elem: _emptyBlockElem,
|
elem: _emptyBlockElem,
|
||||||
node: blockNode,
|
node: blockNode,
|
||||||
@@ -134,7 +137,7 @@ function useAddBlockHandle(
|
|||||||
// Check if blockNode is an empty text node
|
// Check if blockNode is an empty text node
|
||||||
let isEmptyParagraph = true
|
let isEmptyParagraph = true
|
||||||
if (
|
if (
|
||||||
hoveredElementToUse.node.getType() !== 'paragraph' ||
|
hoveredElementToUse?.node.getType() !== 'paragraph' ||
|
||||||
hoveredElementToUse.node.getTextContent() !== ''
|
hoveredElementToUse.node.getTextContent() !== ''
|
||||||
) {
|
) {
|
||||||
isEmptyParagraph = false
|
isEmptyParagraph = false
|
||||||
@@ -142,11 +145,11 @@ function useAddBlockHandle(
|
|||||||
|
|
||||||
if (!isEmptyParagraph) {
|
if (!isEmptyParagraph) {
|
||||||
const newParagraph = $createParagraphNode()
|
const newParagraph = $createParagraphNode()
|
||||||
hoveredElementToUse.node.insertAfter(newParagraph)
|
hoveredElementToUse?.node.insertAfter(newParagraph)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
hoveredElementToUse = {
|
hoveredElementToUse = {
|
||||||
elem: editor.getElementByKey(newParagraph.getKey()),
|
elem: editor.getElementByKey(newParagraph.getKey())!,
|
||||||
node: newParagraph,
|
node: newParagraph,
|
||||||
}
|
}
|
||||||
setHoveredElement(hoveredElementToUse)
|
setHoveredElement(hoveredElementToUse)
|
||||||
@@ -160,7 +163,7 @@ function useAddBlockHandle(
|
|||||||
editor.focus()
|
editor.focus()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hoveredElementToUse.node &&
|
hoveredElementToUse?.node &&
|
||||||
'select' in hoveredElementToUse.node &&
|
'select' in hoveredElementToUse.node &&
|
||||||
typeof hoveredElementToUse.node.select === 'function'
|
typeof hoveredElementToUse.node.select === 'function'
|
||||||
) {
|
) {
|
||||||
@@ -173,7 +176,7 @@ function useAddBlockHandle(
|
|||||||
// Otherwise, this won't work
|
// Otherwise, this won't work
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editor.dispatchCommand(ENABLE_SLASH_MENU_COMMAND, {
|
editor.dispatchCommand(ENABLE_SLASH_MENU_COMMAND, {
|
||||||
node: hoveredElementToUse.node as ParagraphNode,
|
node: hoveredElementToUse?.node as ParagraphNode,
|
||||||
})
|
})
|
||||||
}, 2)
|
}, 2)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
export function debounce(func: Function, wait: number) {
|
export function debounce(func: (...args: any[]) => void, wait: number) {
|
||||||
let timeout: null | number = null
|
let timeout
|
||||||
return function (...args: any[]) {
|
return function (...args: any[]) {
|
||||||
const later = () => {
|
const later = () => {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function getBoundingClientRectWithoutTransform(elem: HTMLElement): DOMRec
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lastNumberOfTransformValue = transformValue.split(',').pop()
|
const lastNumberOfTransformValue = transformValue.split(',').pop()
|
||||||
rect.y = rect.y - Number(lastNumberOfTransformValue.replace(')', ''))
|
rect.y = rect.y - Number(lastNumberOfTransformValue?.replace(')', ''))
|
||||||
|
|
||||||
// Return the original bounding rect if no translation is applied
|
// Return the original bounding rect if no translation is applied
|
||||||
return rect
|
return rect
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ function useDraggableBlockMenu(
|
|||||||
boundingBox?: DOMRect
|
boundingBox?: DOMRect
|
||||||
elem: HTMLElement | null
|
elem: HTMLElement | null
|
||||||
isBelow: boolean
|
isBelow: boolean
|
||||||
}>(null)
|
} | null>(null)
|
||||||
|
|
||||||
const { editorConfig } = useEditorConfigContext()
|
const { editorConfig } = useEditorConfigContext()
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ function useDraggableBlockMenu(
|
|||||||
: -(menuRef?.current?.getBoundingClientRect()?.width ?? 0)),
|
: -(menuRef?.current?.getBoundingClientRect()?.width ?? 0)),
|
||||||
targetLineElem,
|
targetLineElem,
|
||||||
targetBlockElem,
|
targetBlockElem,
|
||||||
lastTargetBlock,
|
lastTargetBlock!,
|
||||||
pageY,
|
pageY,
|
||||||
anchorElem,
|
anchorElem,
|
||||||
event,
|
event,
|
||||||
@@ -243,8 +243,8 @@ function useDraggableBlockMenu(
|
|||||||
isBelow,
|
isBelow,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else if (lastTargetBlock?.elem) {
|
||||||
hideTargetLine(targetLineElem, lastTargetBlock?.elem)
|
hideTargetLine(targetLineElem, lastTargetBlock.elem)
|
||||||
setLastTargetBlock({
|
setLastTargetBlock({
|
||||||
boundingBox: targetBlockElem.getBoundingClientRect(),
|
boundingBox: targetBlockElem.getBoundingClientRect(),
|
||||||
elem: targetBlockElem,
|
elem: targetBlockElem,
|
||||||
@@ -343,8 +343,10 @@ function useDraggableBlockMenu(
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// add new temp html element to newInsertedElem with the same height and width and the class block-selected
|
// add new temp html element to newInsertedElem with the same height and width and the class block-selected
|
||||||
// to highlight the new inserted element
|
// to highlight the new inserted element
|
||||||
const newInsertedElemRect = newInsertedElem.getBoundingClientRect()
|
const newInsertedElemRect = newInsertedElem?.getBoundingClientRect()
|
||||||
|
if (!newInsertedElemRect) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const highlightElem = document.createElement('div')
|
const highlightElem = document.createElement('div')
|
||||||
highlightElem.className = 'lexical-block-highlighter'
|
highlightElem.className = 'lexical-block-highlighter'
|
||||||
|
|
||||||
@@ -414,7 +416,9 @@ function useDraggableBlockMenu(
|
|||||||
|
|
||||||
function onDragEnd(): void {
|
function onDragEnd(): void {
|
||||||
isDraggingBlockRef.current = false
|
isDraggingBlockRef.current = false
|
||||||
hideTargetLine(targetLineRef.current, lastTargetBlock?.elem)
|
if (lastTargetBlock?.elem) {
|
||||||
|
hideTargetLine(targetLineRef.current, lastTargetBlock?.elem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ export function getNodeCloseToPoint(props: Props): Output {
|
|||||||
point: { x, y },
|
point: { x, y },
|
||||||
startIndex = 0,
|
startIndex = 0,
|
||||||
useEdgeAsDefault = false,
|
useEdgeAsDefault = false,
|
||||||
verbose = false,
|
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
// Use cache
|
// Use cache
|
||||||
@@ -104,12 +103,12 @@ export function getNodeCloseToPoint(props: Props): Output {
|
|||||||
editor.getElementByKey(topLevelNodeKeys[topLevelNodeKeys.length - 1]),
|
editor.getElementByKey(topLevelNodeKeys[topLevelNodeKeys.length - 1]),
|
||||||
]
|
]
|
||||||
|
|
||||||
const [firstNodeRect, lastNodeRect] = [
|
if (firstNode && lastNode) {
|
||||||
getBoundingClientRectWithoutTransform(firstNode),
|
const [firstNodeRect, lastNodeRect] = [
|
||||||
getBoundingClientRectWithoutTransform(lastNode),
|
getBoundingClientRectWithoutTransform(firstNode),
|
||||||
]
|
getBoundingClientRectWithoutTransform(lastNode),
|
||||||
|
]
|
||||||
|
|
||||||
if (firstNodeRect && lastNodeRect) {
|
|
||||||
if (y < firstNodeRect.top) {
|
if (y < firstNodeRect.top) {
|
||||||
closestBlockElem.blockElem = firstNode
|
closestBlockElem.blockElem = firstNode
|
||||||
closestBlockElem.distance = firstNodeRect.top - y
|
closestBlockElem.distance = firstNodeRect.top - y
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function setFloatingElemPosition(args: {
|
|||||||
specialHandlingForCaret?: boolean
|
specialHandlingForCaret?: boolean
|
||||||
targetRect: ClientRect | null
|
targetRect: ClientRect | null
|
||||||
verticalGap?: number
|
verticalGap?: number
|
||||||
}): number {
|
}): number | undefined {
|
||||||
const {
|
const {
|
||||||
alwaysDisplayOnTop = false,
|
alwaysDisplayOnTop = false,
|
||||||
anchorElem,
|
anchorElem,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const populate = async ({
|
|||||||
collectionSlug: string
|
collectionSlug: string
|
||||||
id: number | string
|
id: number | string
|
||||||
} & Arguments): Promise<void> => {
|
} & Arguments): Promise<void> => {
|
||||||
const shouldPopulate = depth && currentDepth <= depth
|
const shouldPopulate = depth && currentDepth! <= depth
|
||||||
// usually depth is checked within recursivelyPopulateFieldsForGraphQL. But since this populate function can be called outside of that (in rest afterRead node hooks) we need to check here too
|
// usually depth is checked within recursivelyPopulateFieldsForGraphQL. But since this populate function can be called outside of that (in rest afterRead node hooks) we need to check here too
|
||||||
if (!shouldPopulate) {
|
if (!shouldPopulate) {
|
||||||
return
|
return
|
||||||
@@ -36,18 +36,18 @@ export const populate = async ({
|
|||||||
|
|
||||||
const dataRef = data as Record<string, unknown>
|
const dataRef = data as Record<string, unknown>
|
||||||
|
|
||||||
const doc = await req.payloadDataLoader.load(
|
const doc = await req.payloadDataLoader?.load(
|
||||||
createDataloaderCacheKey({
|
createDataloaderCacheKey({
|
||||||
collectionSlug,
|
collectionSlug,
|
||||||
currentDepth: currentDepth + 1,
|
currentDepth: currentDepth! + 1,
|
||||||
depth,
|
depth,
|
||||||
docID: id as string,
|
docID: id as string,
|
||||||
draft,
|
draft,
|
||||||
fallbackLocale: req.fallbackLocale,
|
fallbackLocale: req.fallbackLocale!,
|
||||||
locale: req.locale,
|
locale: req.locale!,
|
||||||
overrideAccess,
|
overrideAccess,
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
transactionID: req.transactionID,
|
transactionID: req.transactionID!,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import { recurseNodes } from '../utilities/forEachNodeRecursively.js'
|
|||||||
|
|
||||||
export type Args = {
|
export type Args = {
|
||||||
editorPopulationPromises: Map<string, Array<PopulationPromise>>
|
editorPopulationPromises: Map<string, Array<PopulationPromise>>
|
||||||
} & Parameters<RichTextAdapter<SerializedEditorState, AdapterProps>['graphQLPopulationPromises']>[0]
|
} & Parameters<
|
||||||
|
NonNullable<RichTextAdapter<SerializedEditorState, AdapterProps>['graphQLPopulationPromises']>
|
||||||
|
>[0]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends all new populationPromises to the populationPromises prop
|
* Appends all new populationPromises to the populationPromises prop
|
||||||
@@ -29,7 +31,7 @@ export const populateLexicalPopulationPromises = ({
|
|||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
siblingDoc,
|
siblingDoc,
|
||||||
}: Args) => {
|
}: Args) => {
|
||||||
const shouldPopulate = depth && currentDepth <= depth
|
const shouldPopulate = depth && currentDepth! <= depth
|
||||||
|
|
||||||
if (!shouldPopulate) {
|
if (!shouldPopulate) {
|
||||||
return
|
return
|
||||||
@@ -37,11 +39,12 @@ export const populateLexicalPopulationPromises = ({
|
|||||||
|
|
||||||
recurseNodes({
|
recurseNodes({
|
||||||
callback: (node) => {
|
callback: (node) => {
|
||||||
if (editorPopulationPromises?.has(node.type)) {
|
const editorPopulationPromisesOfNodeType = editorPopulationPromises?.get(node.type)
|
||||||
for (const promise of editorPopulationPromises.get(node.type)) {
|
if (editorPopulationPromisesOfNodeType) {
|
||||||
|
for (const promise of editorPopulationPromisesOfNodeType) {
|
||||||
promise({
|
promise({
|
||||||
context,
|
context,
|
||||||
currentDepth,
|
currentDepth: currentDepth!,
|
||||||
depth,
|
depth,
|
||||||
draft,
|
draft,
|
||||||
editorPopulationPromises,
|
editorPopulationPromises,
|
||||||
@@ -50,7 +53,7 @@ export const populateLexicalPopulationPromises = ({
|
|||||||
findMany,
|
findMany,
|
||||||
flattenLocales,
|
flattenLocales,
|
||||||
node,
|
node,
|
||||||
overrideAccess,
|
overrideAccess: overrideAccess!,
|
||||||
populationPromises,
|
populationPromises,
|
||||||
req,
|
req,
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ export const recursivelyPopulateFieldsForGraphQL = ({
|
|||||||
depth,
|
depth,
|
||||||
doc: data as any, // Looks like it's only needed for hooks and access control, so doesn't matter what we pass here right now
|
doc: data as any, // Looks like it's only needed for hooks and access control, so doesn't matter what we pass here right now
|
||||||
draft,
|
draft,
|
||||||
fallbackLocale: req.fallbackLocale,
|
fallbackLocale: req.fallbackLocale!,
|
||||||
fieldPromises,
|
fieldPromises,
|
||||||
fields,
|
fields,
|
||||||
findMany,
|
findMany,
|
||||||
flattenLocales,
|
flattenLocales,
|
||||||
global: null, // Pass from core? This is only needed for hooks, so we can leave this null for now
|
global: null, // Pass from core? This is only needed for hooks, so we can leave this null for now
|
||||||
locale: req.locale,
|
locale: req.locale!,
|
||||||
overrideAccess,
|
overrideAccess,
|
||||||
path: [],
|
path: [],
|
||||||
populationPromises, // This is not the same as populationPromises passed into this recurseNestedFields. These are just promises resolved at the very end.
|
populationPromises, // This is not the same as populationPromises passed into this recurseNestedFields. These are just promises resolved at the very end.
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FormProps } from '@payloadcms/ui'
|
|
||||||
import type { ClientField, FormState } from 'payload'
|
import type { ClientField, FormState } from 'payload'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -42,14 +41,14 @@ export const DrawerContent: React.FC<Omit<FieldsDrawerProps, 'drawerSlug' | 'dra
|
|||||||
`${schemaPath}.lexical_internal_feature.${featureKey}${schemaPathSuffix ? `.${schemaPathSuffix}` : ''}`
|
`${schemaPath}.lexical_internal_feature.${featureKey}${schemaPathSuffix ? `.${schemaPathSuffix}` : ''}`
|
||||||
|
|
||||||
const fields: any =
|
const fields: any =
|
||||||
fieldMapOverride ?? (richTextComponentMap.get(componentMapRenderedFieldsPath) as ClientField[]) // Field Schema
|
fieldMapOverride ?? (richTextComponentMap?.get(componentMapRenderedFieldsPath) as ClientField[]) // Field Schema
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const awaitInitialState = async () => {
|
const awaitInitialState = async () => {
|
||||||
const { state } = await getFormState({
|
const { state } = await getFormState({
|
||||||
apiRoute: config.routes.api,
|
apiRoute: config.routes.api,
|
||||||
body: {
|
body: {
|
||||||
id,
|
id: id!,
|
||||||
data: data ?? {},
|
data: data ?? {},
|
||||||
operation: 'update',
|
operation: 'update',
|
||||||
schemaPath: schemaFieldsPath,
|
schemaPath: schemaFieldsPath,
|
||||||
@@ -63,12 +62,12 @@ export const DrawerContent: React.FC<Omit<FieldsDrawerProps, 'drawerSlug' | 'dra
|
|||||||
void awaitInitialState()
|
void awaitInitialState()
|
||||||
}, [config.routes.api, config.serverURL, schemaFieldsPath, id, data])
|
}, [config.routes.api, config.serverURL, schemaFieldsPath, id, data])
|
||||||
|
|
||||||
const onChange: FormProps['onChange'][0] = useCallback(
|
const onChange = useCallback(
|
||||||
async ({ formState: prevFormState }) => {
|
async ({ formState: prevFormState }) => {
|
||||||
const { state } = await getFormState({
|
const { state } = await getFormState({
|
||||||
apiRoute: config.routes.api,
|
apiRoute: config.routes.api,
|
||||||
body: {
|
body: {
|
||||||
id,
|
id: id!,
|
||||||
formState: prevFormState,
|
formState: prevFormState,
|
||||||
operation: 'update',
|
operation: 'update',
|
||||||
schemaPath: schemaFieldsPath,
|
schemaPath: schemaFieldsPath,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export const getGenerateComponentMap =
|
|||||||
for (const componentKey in components) {
|
for (const componentKey in components) {
|
||||||
const payloadComponent = components[componentKey]
|
const payloadComponent = components[componentKey]
|
||||||
|
|
||||||
|
// @ts-expect-error - TODO: fix this
|
||||||
const mappedComponent: MappedComponent = createMappedComponent(
|
const mappedComponent: MappedComponent = createMappedComponent(
|
||||||
payloadComponent,
|
payloadComponent,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ import type { Field } from 'payload'
|
|||||||
|
|
||||||
import { fieldAffectsData, fieldHasSubFields, fieldIsArrayType, tabHasName } from 'payload/shared'
|
import { fieldAffectsData, fieldHasSubFields, fieldIsArrayType, tabHasName } from 'payload/shared'
|
||||||
|
|
||||||
import type { SlateNodeConverter } from '../../features/migrations/slateToLexical/converter/types.js'
|
import type {
|
||||||
|
SlateNode,
|
||||||
|
SlateNodeConverter,
|
||||||
|
} from '../../features/migrations/slateToLexical/converter/types.js'
|
||||||
import type { LexicalRichTextAdapter } from '../../types.js'
|
import type { LexicalRichTextAdapter } from '../../types.js'
|
||||||
|
|
||||||
import { convertSlateToLexical } from '../../features/migrations/slateToLexical/converter/index.js'
|
import { convertSlateToLexical } from '../../features/migrations/slateToLexical/converter/index.js'
|
||||||
|
|
||||||
type NestedRichTextFieldsArgs = {
|
type NestedRichTextFieldsArgs = {
|
||||||
data: unknown
|
data: Record<string, unknown>
|
||||||
|
|
||||||
fields: Field[]
|
fields: Field[]
|
||||||
found: number
|
found: number
|
||||||
@@ -23,7 +26,7 @@ export const migrateDocumentFieldsRecursively = ({
|
|||||||
if (fieldHasSubFields(field) && !fieldIsArrayType(field)) {
|
if (fieldHasSubFields(field) && !fieldIsArrayType(field)) {
|
||||||
if (fieldAffectsData(field) && typeof data[field.name] === 'object') {
|
if (fieldAffectsData(field) && typeof data[field.name] === 'object') {
|
||||||
found += migrateDocumentFieldsRecursively({
|
found += migrateDocumentFieldsRecursively({
|
||||||
data: data[field.name],
|
data: data[field.name] as Record<string, unknown>,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
found,
|
found,
|
||||||
})
|
})
|
||||||
@@ -37,18 +40,18 @@ export const migrateDocumentFieldsRecursively = ({
|
|||||||
} else if (field.type === 'tabs') {
|
} else if (field.type === 'tabs') {
|
||||||
field.tabs.forEach((tab) => {
|
field.tabs.forEach((tab) => {
|
||||||
found += migrateDocumentFieldsRecursively({
|
found += migrateDocumentFieldsRecursively({
|
||||||
data: tabHasName(tab) ? data[tab.name] : data,
|
data: (tabHasName(tab) ? data[tab.name] : data) as Record<string, unknown>,
|
||||||
fields: tab.fields,
|
fields: tab.fields,
|
||||||
found,
|
found,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else if (Array.isArray(data[field.name])) {
|
} else if (Array.isArray(data[field.name])) {
|
||||||
if (field.type === 'blocks') {
|
if (field.type === 'blocks') {
|
||||||
data[field.name].forEach((row, i) => {
|
;(data[field.name] as Array<Record<string, unknown>>).forEach((row, i) => {
|
||||||
const block = field.blocks.find(({ slug }) => slug === row?.blockType)
|
const block = field.blocks.find(({ slug }) => slug === row?.blockType)
|
||||||
if (block) {
|
if (block) {
|
||||||
found += migrateDocumentFieldsRecursively({
|
found += migrateDocumentFieldsRecursively({
|
||||||
data: data[field.name][i],
|
data: (data[field.name] as Array<Record<string, unknown>>)[i],
|
||||||
fields: block.fields,
|
fields: block.fields,
|
||||||
found,
|
found,
|
||||||
})
|
})
|
||||||
@@ -57,9 +60,9 @@ export const migrateDocumentFieldsRecursively = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'array') {
|
if (field.type === 'array') {
|
||||||
data[field.name].forEach((_, i) => {
|
;(data[field.name] as Array<Record<string, unknown>>).forEach((_, i) => {
|
||||||
found += migrateDocumentFieldsRecursively({
|
found += migrateDocumentFieldsRecursively({
|
||||||
data: data[field.name][i],
|
data: (data[field.name] as Array<Record<string, unknown>>)[i],
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
found,
|
found,
|
||||||
})
|
})
|
||||||
@@ -82,8 +85,8 @@ export const migrateDocumentFieldsRecursively = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
data[field.name] = convertSlateToLexical({
|
data[field.name] = convertSlateToLexical({
|
||||||
converters,
|
converters: converters!,
|
||||||
slateData: data[field.name],
|
slateData: data[field.name] as SlateNode[],
|
||||||
})
|
})
|
||||||
|
|
||||||
found++
|
found++
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type { LexicalRichTextAdapter } from '../../types.js'
|
|||||||
import { getEnabledNodes } from '../../lexical/nodes/index.js'
|
import { getEnabledNodes } from '../../lexical/nodes/index.js'
|
||||||
|
|
||||||
type NestedRichTextFieldsArgs = {
|
type NestedRichTextFieldsArgs = {
|
||||||
data: unknown
|
data: Record<string, unknown>
|
||||||
|
|
||||||
fields: Field[]
|
fields: Field[]
|
||||||
found: number
|
found: number
|
||||||
@@ -24,7 +24,7 @@ export const upgradeDocumentFieldsRecursively = ({
|
|||||||
if (fieldHasSubFields(field) && !fieldIsArrayType(field)) {
|
if (fieldHasSubFields(field) && !fieldIsArrayType(field)) {
|
||||||
if (fieldAffectsData(field) && typeof data[field.name] === 'object') {
|
if (fieldAffectsData(field) && typeof data[field.name] === 'object') {
|
||||||
found += upgradeDocumentFieldsRecursively({
|
found += upgradeDocumentFieldsRecursively({
|
||||||
data: data[field.name],
|
data: data[field.name] as Record<string, unknown>,
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
found,
|
found,
|
||||||
})
|
})
|
||||||
@@ -38,18 +38,18 @@ export const upgradeDocumentFieldsRecursively = ({
|
|||||||
} else if (field.type === 'tabs') {
|
} else if (field.type === 'tabs') {
|
||||||
field.tabs.forEach((tab) => {
|
field.tabs.forEach((tab) => {
|
||||||
found += upgradeDocumentFieldsRecursively({
|
found += upgradeDocumentFieldsRecursively({
|
||||||
data: tabHasName(tab) ? data[tab.name] : data,
|
data: (tabHasName(tab) ? data[tab.name] : data) as Record<string, unknown>,
|
||||||
fields: tab.fields,
|
fields: tab.fields,
|
||||||
found,
|
found,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else if (Array.isArray(data[field.name])) {
|
} else if (Array.isArray(data[field.name])) {
|
||||||
if (field.type === 'blocks') {
|
if (field.type === 'blocks') {
|
||||||
data[field.name].forEach((row, i) => {
|
;(data[field.name] as Record<string, unknown>[]).forEach((row, i) => {
|
||||||
const block = field.blocks.find(({ slug }) => slug === row?.blockType)
|
const block = field.blocks.find(({ slug }) => slug === row?.blockType)
|
||||||
if (block) {
|
if (block) {
|
||||||
found += upgradeDocumentFieldsRecursively({
|
found += upgradeDocumentFieldsRecursively({
|
||||||
data: data[field.name][i],
|
data: (data[field.name] as Record<string, unknown>[])[i],
|
||||||
fields: block.fields,
|
fields: block.fields,
|
||||||
found,
|
found,
|
||||||
})
|
})
|
||||||
@@ -58,9 +58,9 @@ export const upgradeDocumentFieldsRecursively = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === 'array') {
|
if (field.type === 'array') {
|
||||||
data[field.name].forEach((_, i) => {
|
;(data[field.name] as Record<string, unknown>[]).forEach((_, i) => {
|
||||||
found += upgradeDocumentFieldsRecursively({
|
found += upgradeDocumentFieldsRecursively({
|
||||||
data: data[field.name][i],
|
data: (data[field.name] as Record<string, unknown>[])[i],
|
||||||
fields: field.fields,
|
fields: field.fields,
|
||||||
found,
|
found,
|
||||||
})
|
})
|
||||||
@@ -72,14 +72,14 @@ export const upgradeDocumentFieldsRecursively = ({
|
|||||||
field.type === 'richText' &&
|
field.type === 'richText' &&
|
||||||
data[field.name] &&
|
data[field.name] &&
|
||||||
!Array.isArray(data[field.name]) &&
|
!Array.isArray(data[field.name]) &&
|
||||||
'root' in data[field.name]
|
'root' in (data[field.name] as Record<string, unknown>)
|
||||||
) {
|
) {
|
||||||
// Lexical richText
|
// Lexical richText
|
||||||
const editor: LexicalRichTextAdapter = field.editor as LexicalRichTextAdapter
|
const editor: LexicalRichTextAdapter = field.editor as LexicalRichTextAdapter
|
||||||
if (editor && typeof editor === 'object') {
|
if (editor && typeof editor === 'object') {
|
||||||
if ('features' in editor && editor.features?.length) {
|
if ('features' in editor && editor.features?.length) {
|
||||||
// Load lexical editor into lexical, then save it immediately
|
// Load lexical editor into lexical, then save it immediately
|
||||||
const editorState: SerializedEditorState = data[field.name]
|
const editorState = data[field.name] as SerializedEditorState
|
||||||
|
|
||||||
const headlessEditor = createHeadlessEditor({
|
const headlessEditor = createHeadlessEditor({
|
||||||
nodes: getEnabledNodes({
|
nodes: getEnabledNodes({
|
||||||
|
|||||||
@@ -17,12 +17,8 @@ export async function validateNodes({
|
|||||||
}): Promise<string | true> {
|
}): Promise<string | true> {
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
// Validate node
|
// Validate node
|
||||||
if (
|
const validations = nodeValidations.get(node.type)
|
||||||
nodeValidations &&
|
if (validations) {
|
||||||
typeof nodeValidations?.has === 'function' &&
|
|
||||||
nodeValidations?.has(node.type)
|
|
||||||
) {
|
|
||||||
const validations = nodeValidations.get(node.type)
|
|
||||||
for (const validation of validations) {
|
for (const validation of validations) {
|
||||||
const validationResult = await validation({
|
const validationResult = await validation({
|
||||||
node,
|
node,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"noEmit": false /* Do not emit outputs. */,
|
"noEmit": false /* Do not emit outputs. */,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export const NullifyLocaleField: React.FC<NullifyLocaleFieldProps> = ({
|
|||||||
<CheckboxField
|
<CheckboxField
|
||||||
checked={checked}
|
checked={checked}
|
||||||
field={{
|
field={{
|
||||||
|
name: '',
|
||||||
label: t('general:fallbackToDefaultLocale'),
|
label: t('general:fallbackToDefaultLocale'),
|
||||||
}}
|
}}
|
||||||
id={`field-${path.replace(/\./g, '__')}`}
|
id={`field-${path.replace(/\./g, '__')}`}
|
||||||
|
|||||||
Reference in New Issue
Block a user