Skip to content

Commit d9bb51f

Browse files
authored
feat(richtext-lexical)!: initialize lexical during sanitization (#6119)
BREAKING: - sanitizeFields is now an async function - the richText adapters now return a function instead of returning the adapter directly
1 parent 9a636a3 commit d9bb51f

File tree

44 files changed

+812
-778
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+812
-778
lines changed

packages/db-mongodb/src/queries/getLocalizedSortProperty.spec.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ import { SanitizedConfig, sanitizeConfig } from 'payload/config'
22
import { Config } from 'payload/config'
33
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
44

5-
const config = sanitizeConfig({
6-
localization: {
7-
locales: ['en', 'es'],
8-
defaultLocale: 'en',
9-
fallback: true,
10-
},
11-
} as Config) as SanitizedConfig
5+
let config: SanitizedConfig
126

137
describe('get localized sort property', () => {
14-
it('passes through a non-localized sort property', () => {
8+
beforeAll(async () => {
9+
config = (await sanitizeConfig({
10+
localization: {
11+
locales: ['en', 'es'],
12+
defaultLocale: 'en',
13+
fallback: true,
14+
},
15+
} as Config)) as SanitizedConfig
16+
})
17+
it('passes through a non-localized sort property', async () => {
1518
const result = getLocalizedSortProperty({
1619
segments: ['title'],
1720
config,

packages/graphql/src/schema/buildObjectType.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,10 @@ function buildObjectType({
462462
async resolve(parent, args, context: Context) {
463463
let depth = config.defaultDepth
464464
if (typeof args.depth !== 'undefined') depth = args.depth
465+
if (typeof field?.editor === 'function') {
466+
throw new Error('Attempted to access unsanitized rich text editor.')
467+
}
468+
465469
const editor: RichTextAdapter = field?.editor
466470

467471
// RichText fields have their own depth argument in GraphQL.

packages/next/src/utilities/buildFieldSchemaMap/traverseFields.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ export const traverseFields = ({
6868
break
6969

7070
case 'richText':
71+
if (typeof field.editor === 'function') {
72+
throw new Error('Attempted to access unsanitized rich text editor.')
73+
}
74+
7175
if (typeof field.editor.generateSchemaMap === 'function') {
7276
field.editor.generateSchemaMap({
7377
config,

packages/payload/src/admin/RichText.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { I18n } from '@payloadcms/translations'
22
import type { JSONSchema4 } from 'json-schema'
33

4-
import type { SanitizedConfig } from '../config/types.js'
4+
import type { Config, SanitizedConfig } from '../config/types.js'
55
import type { Field, FieldBase, RichTextField, Validate } from '../fields/config/types.js'
66
import type { PayloadRequestWithData, RequestContext } from '../types/index.js'
77
import type { WithServerSideProps } from './elements/WithServerSideProps.js'
@@ -84,3 +84,15 @@ export type RichTextAdapter<
8484
CellComponent: React.FC<any>
8585
FieldComponent: React.FC<RichTextFieldProps<Value, AdapterProps, ExtraFieldProperties>>
8686
}
87+
88+
export type RichTextAdapterProvider<
89+
Value extends object = object,
90+
AdapterProps = any,
91+
ExtraFieldProperties = {},
92+
> = ({
93+
config,
94+
}: {
95+
config: SanitizedConfig
96+
}) =>
97+
| Promise<RichTextAdapter<Value, AdapterProps, ExtraFieldProperties>>
98+
| RichTextAdapter<Value, AdapterProps, ExtraFieldProperties>

packages/payload/src/admin/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type { RichTextAdapter, RichTextFieldProps } from './RichText.js'
1+
export type { RichTextAdapter, RichTextAdapterProvider, RichTextFieldProps } from './RichText.js'
22
export type { CellComponentProps, DefaultCellComponentProps } from './elements/Cell.js'
33
export type { ConditionalDateProps } from './elements/DatePicker.js'
44
export type { DayPickerProps, SharedProps, TimePickerProps } from './elements/DatePicker.js'

packages/payload/src/collections/config/sanitize.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import merge from 'deepmerge'
22

3-
import type { Config } from '../../config/types.js'
3+
import type { Config, SanitizedConfig } from '../../config/types.js'
44
import type { CollectionConfig, SanitizedCollectionConfig } from './types.js'
55

66
import baseAccountLockFields from '../../auth/baseFields/accountLock.js'
@@ -17,10 +17,15 @@ import { isPlainObject } from '../../utilities/isPlainObject.js'
1717
import baseVersionFields from '../../versions/baseFields.js'
1818
import { authDefaults, defaults } from './defaults.js'
1919

20-
export const sanitizeCollection = (
20+
export const sanitizeCollection = async (
2121
config: Config,
2222
collection: CollectionConfig,
23-
): SanitizedCollectionConfig => {
23+
/**
24+
* If this property is set, RichText fields won't be sanitized immediately. Instead, they will be added to this array as promises
25+
* so that you can sanitize them together, after the config has been sanitized.
26+
*/
27+
richTextSanitizationPromises?: Array<(config: SanitizedConfig) => Promise<void>>,
28+
): Promise<SanitizedCollectionConfig> => {
2429
// /////////////////////////////////
2530
// Make copy of collection config
2631
// /////////////////////////////////
@@ -151,9 +156,10 @@ export const sanitizeCollection = (
151156
// /////////////////////////////////
152157

153158
const validRelationships = config.collections.map((c) => c.slug) || []
154-
sanitized.fields = sanitizeFields({
159+
sanitized.fields = await sanitizeFields({
155160
config,
156161
fields: sanitized.fields,
162+
richTextSanitizationPromises,
157163
validRelationships,
158164
})
159165

packages/payload/src/config/build.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/* eslint-disable no-use-before-define */
2-
/* eslint-disable no-nested-ternary */
31
import type { Config, SanitizedConfig } from './types.js'
42

53
import { sanitizeConfig } from './sanitize.js'
@@ -16,10 +14,8 @@ export async function buildConfig(config: Config): Promise<SanitizedConfig> {
1614
return plugin(configAfterPlugin)
1715
}, Promise.resolve(config))
1816

19-
const sanitizedConfig = sanitizeConfig(configAfterPlugins)
20-
21-
return sanitizedConfig
17+
return await sanitizeConfig(configAfterPlugins)
2218
}
2319

24-
return sanitizeConfig(config)
20+
return await sanitizeConfig(config)
2521
}

packages/payload/src/config/sanitize.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { defaultUserCollection } from '../auth/defaultUser.js'
1212
import { sanitizeCollection } from '../collections/config/sanitize.js'
1313
import { migrationsCollection } from '../database/migrations/migrationsCollection.js'
1414
import { InvalidConfiguration } from '../errors/index.js'
15-
import sanitizeGlobals from '../globals/config/sanitize.js'
15+
import { sanitizeGlobals } from '../globals/config/sanitize.js'
1616
import getPreferencesCollection from '../preferences/preferencesCollection.js'
1717
import checkDuplicateCollections from '../utilities/checkDuplicateCollections.js'
1818
import { isPlainObject } from '../utilities/isPlainObject.js'
@@ -41,10 +41,10 @@ const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig>
4141
)
4242
}
4343

44-
return sanitizedConfig as Partial<SanitizedConfig>
44+
return sanitizedConfig as unknown as Partial<SanitizedConfig>
4545
}
4646

47-
export const sanitizeConfig = (incomingConfig: Config): SanitizedConfig => {
47+
export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedConfig> => {
4848
const configWithDefaults: Config = merge(defaults, incomingConfig, {
4949
isMergeableObject: isPlainObject,
5050
}) as Config
@@ -97,16 +97,25 @@ export const sanitizeConfig = (incomingConfig: Config): SanitizedConfig => {
9797
...(incomingConfig?.i18n ?? {}),
9898
}
9999

100-
configWithDefaults.collections.push(getPreferencesCollection(configWithDefaults))
100+
configWithDefaults.collections.push(getPreferencesCollection(config as unknown as Config))
101101
configWithDefaults.collections.push(migrationsCollection)
102102

103-
config.collections = config.collections.map((collection) =>
104-
sanitizeCollection(configWithDefaults, collection),
105-
)
103+
const richTextSanitizationPromises: Array<(config: SanitizedConfig) => Promise<void>> = []
104+
for (let i = 0; i < config.collections.length; i++) {
105+
config.collections[i] = await sanitizeCollection(
106+
config as unknown as Config,
107+
config.collections[i],
108+
richTextSanitizationPromises,
109+
)
110+
}
111+
106112
checkDuplicateCollections(config.collections)
107113

108114
if (config.globals.length > 0) {
109-
config.globals = sanitizeGlobals(config as SanitizedConfig)
115+
config.globals = await sanitizeGlobals(
116+
config as unknown as Config,
117+
richTextSanitizationPromises,
118+
)
110119
}
111120

112121
if (config.serverURL !== '') {
@@ -119,5 +128,20 @@ export const sanitizeConfig = (incomingConfig: Config): SanitizedConfig => {
119128
new Set(config.collections.map((c) => c.upload?.adapter).filter(Boolean)),
120129
)
121130

131+
/*
132+
Execute richText sanitization
133+
*/
134+
if (typeof incomingConfig.editor === 'function') {
135+
config.editor = await incomingConfig.editor({
136+
config: config as SanitizedConfig,
137+
})
138+
}
139+
140+
const promises: Promise<void>[] = []
141+
for (const sanitizeFunction of richTextSanitizationPromises) {
142+
promises.push(sanitizeFunction(config as SanitizedConfig))
143+
}
144+
await Promise.all(promises)
145+
122146
return config as SanitizedConfig
123147
}

packages/payload/src/config/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type React from 'react'
66
import type { default as sharp } from 'sharp'
77
import type { DeepRequired } from 'ts-essentials'
88

9+
import type { RichTextAdapterProvider } from '../admin/RichText.js'
910
import type { DocumentTab, RichTextAdapter } from '../admin/types.js'
1011
import type { AdminView, ServerSideEditViewProps } from '../admin/views/types.js'
1112
import type { User } from '../auth/types.js'
@@ -527,7 +528,7 @@ export type Config = {
527528
*/
528529
defaultMaxTextLength?: number
529530
/** Default richtext editor to use for richText fields */
530-
editor: RichTextAdapter<any, any, any>
531+
editor: RichTextAdapterProvider<any, any, any>
531532
/**
532533
* Email Adapter
533534
*
@@ -640,9 +641,11 @@ export type Config = {
640641

641642
export type SanitizedConfig = Omit<
642643
DeepRequired<Config>,
643-
'collections' | 'endpoint' | 'globals' | 'i18n' | 'localization' | 'upload'
644+
'collections' | 'editor' | 'endpoint' | 'globals' | 'i18n' | 'localization' | 'upload'
644645
> & {
645646
collections: SanitizedCollectionConfig[]
647+
/** Default richtext editor to use for richText fields */
648+
editor: RichTextAdapter<any, any, any>
646649
endpoints: Endpoint[]
647650
globals: SanitizedGlobalConfig[]
648651
i18n: Required<I18nOptions>

packages/payload/src/exports/i18n.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { getLocalI18n } from '../translations/getLocalI18n.js'

0 commit comments

Comments
 (0)