Skip to content

Commit

Permalink
feat(richtext-lexical)!: initialize lexical during sanitization (#6119)
Browse files Browse the repository at this point in the history
BREAKING:

- sanitizeFields is now an async function
- the richText adapters now return a function instead of returning the adapter directly
  • Loading branch information
AlessioGr committed Apr 30, 2024
1 parent 9a636a3 commit d9bb51f
Show file tree
Hide file tree
Showing 44 changed files with 812 additions and 778 deletions.
19 changes: 11 additions & 8 deletions packages/db-mongodb/src/queries/getLocalizedSortProperty.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import { SanitizedConfig, sanitizeConfig } from 'payload/config'
import { Config } from 'payload/config'
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'

const config = sanitizeConfig({
localization: {
locales: ['en', 'es'],
defaultLocale: 'en',
fallback: true,
},
} as Config) as SanitizedConfig
let config: SanitizedConfig

describe('get localized sort property', () => {
it('passes through a non-localized sort property', () => {
beforeAll(async () => {
config = (await sanitizeConfig({
localization: {
locales: ['en', 'es'],
defaultLocale: 'en',
fallback: true,
},
} as Config)) as SanitizedConfig
})
it('passes through a non-localized sort property', async () => {
const result = getLocalizedSortProperty({
segments: ['title'],
config,
Expand Down
4 changes: 4 additions & 0 deletions packages/graphql/src/schema/buildObjectType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,10 @@ function buildObjectType({
async resolve(parent, args, context: Context) {
let depth = config.defaultDepth
if (typeof args.depth !== 'undefined') depth = args.depth
if (typeof field?.editor === 'function') {
throw new Error('Attempted to access unsanitized rich text editor.')
}

const editor: RichTextAdapter = field?.editor

// RichText fields have their own depth argument in GraphQL.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export const traverseFields = ({
break

case 'richText':
if (typeof field.editor === 'function') {
throw new Error('Attempted to access unsanitized rich text editor.')
}

if (typeof field.editor.generateSchemaMap === 'function') {
field.editor.generateSchemaMap({
config,
Expand Down
14 changes: 13 additions & 1 deletion packages/payload/src/admin/RichText.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { I18n } from '@payloadcms/translations'
import type { JSONSchema4 } from 'json-schema'

import type { SanitizedConfig } from '../config/types.js'
import type { Config, SanitizedConfig } from '../config/types.js'
import type { Field, FieldBase, RichTextField, Validate } from '../fields/config/types.js'
import type { PayloadRequestWithData, RequestContext } from '../types/index.js'
import type { WithServerSideProps } from './elements/WithServerSideProps.js'
Expand Down Expand Up @@ -84,3 +84,15 @@ export type RichTextAdapter<
CellComponent: React.FC<any>
FieldComponent: React.FC<RichTextFieldProps<Value, AdapterProps, ExtraFieldProperties>>
}

export type RichTextAdapterProvider<
Value extends object = object,
AdapterProps = any,
ExtraFieldProperties = {},
> = ({
config,
}: {
config: SanitizedConfig
}) =>
| Promise<RichTextAdapter<Value, AdapterProps, ExtraFieldProperties>>
| RichTextAdapter<Value, AdapterProps, ExtraFieldProperties>
2 changes: 1 addition & 1 deletion packages/payload/src/admin/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type { RichTextAdapter, RichTextFieldProps } from './RichText.js'
export type { RichTextAdapter, RichTextAdapterProvider, RichTextFieldProps } from './RichText.js'
export type { CellComponentProps, DefaultCellComponentProps } from './elements/Cell.js'
export type { ConditionalDateProps } from './elements/DatePicker.js'
export type { DayPickerProps, SharedProps, TimePickerProps } from './elements/DatePicker.js'
Expand Down
14 changes: 10 additions & 4 deletions packages/payload/src/collections/config/sanitize.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import merge from 'deepmerge'

import type { Config } from '../../config/types.js'
import type { Config, SanitizedConfig } from '../../config/types.js'
import type { CollectionConfig, SanitizedCollectionConfig } from './types.js'

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

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

const validRelationships = config.collections.map((c) => c.slug) || []
sanitized.fields = sanitizeFields({
sanitized.fields = await sanitizeFields({
config,
fields: sanitized.fields,
richTextSanitizationPromises,
validRelationships,
})

Expand Down
8 changes: 2 additions & 6 deletions packages/payload/src/config/build.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* eslint-disable no-use-before-define */
/* eslint-disable no-nested-ternary */
import type { Config, SanitizedConfig } from './types.js'

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

const sanitizedConfig = sanitizeConfig(configAfterPlugins)

return sanitizedConfig
return await sanitizeConfig(configAfterPlugins)
}

return sanitizeConfig(config)
return await sanitizeConfig(config)
}
40 changes: 32 additions & 8 deletions packages/payload/src/config/sanitize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { defaultUserCollection } from '../auth/defaultUser.js'
import { sanitizeCollection } from '../collections/config/sanitize.js'
import { migrationsCollection } from '../database/migrations/migrationsCollection.js'
import { InvalidConfiguration } from '../errors/index.js'
import sanitizeGlobals from '../globals/config/sanitize.js'
import { sanitizeGlobals } from '../globals/config/sanitize.js'
import getPreferencesCollection from '../preferences/preferencesCollection.js'
import checkDuplicateCollections from '../utilities/checkDuplicateCollections.js'
import { isPlainObject } from '../utilities/isPlainObject.js'
Expand Down Expand Up @@ -41,10 +41,10 @@ const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig>
)
}

return sanitizedConfig as Partial<SanitizedConfig>
return sanitizedConfig as unknown as Partial<SanitizedConfig>
}

export const sanitizeConfig = (incomingConfig: Config): SanitizedConfig => {
export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedConfig> => {
const configWithDefaults: Config = merge(defaults, incomingConfig, {
isMergeableObject: isPlainObject,
}) as Config
Expand Down Expand Up @@ -97,16 +97,25 @@ export const sanitizeConfig = (incomingConfig: Config): SanitizedConfig => {
...(incomingConfig?.i18n ?? {}),
}

configWithDefaults.collections.push(getPreferencesCollection(configWithDefaults))
configWithDefaults.collections.push(getPreferencesCollection(config as unknown as Config))
configWithDefaults.collections.push(migrationsCollection)

config.collections = config.collections.map((collection) =>
sanitizeCollection(configWithDefaults, collection),
)
const richTextSanitizationPromises: Array<(config: SanitizedConfig) => Promise<void>> = []
for (let i = 0; i < config.collections.length; i++) {
config.collections[i] = await sanitizeCollection(
config as unknown as Config,
config.collections[i],
richTextSanitizationPromises,
)
}

checkDuplicateCollections(config.collections)

if (config.globals.length > 0) {
config.globals = sanitizeGlobals(config as SanitizedConfig)
config.globals = await sanitizeGlobals(
config as unknown as Config,
richTextSanitizationPromises,
)
}

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

/*
Execute richText sanitization
*/
if (typeof incomingConfig.editor === 'function') {
config.editor = await incomingConfig.editor({
config: config as SanitizedConfig,
})
}

const promises: Promise<void>[] = []
for (const sanitizeFunction of richTextSanitizationPromises) {
promises.push(sanitizeFunction(config as SanitizedConfig))
}
await Promise.all(promises)

return config as SanitizedConfig
}
7 changes: 5 additions & 2 deletions packages/payload/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type React from 'react'
import type { default as sharp } from 'sharp'
import type { DeepRequired } from 'ts-essentials'

import type { RichTextAdapterProvider } from '../admin/RichText.js'
import type { DocumentTab, RichTextAdapter } from '../admin/types.js'
import type { AdminView, ServerSideEditViewProps } from '../admin/views/types.js'
import type { User } from '../auth/types.js'
Expand Down Expand Up @@ -527,7 +528,7 @@ export type Config = {
*/
defaultMaxTextLength?: number
/** Default richtext editor to use for richText fields */
editor: RichTextAdapter<any, any, any>
editor: RichTextAdapterProvider<any, any, any>
/**
* Email Adapter
*
Expand Down Expand Up @@ -640,9 +641,11 @@ export type Config = {

export type SanitizedConfig = Omit<
DeepRequired<Config>,
'collections' | 'endpoint' | 'globals' | 'i18n' | 'localization' | 'upload'
'collections' | 'editor' | 'endpoint' | 'globals' | 'i18n' | 'localization' | 'upload'
> & {
collections: SanitizedCollectionConfig[]
/** Default richtext editor to use for richText fields */
editor: RichTextAdapter<any, any, any>
endpoints: Endpoint[]
globals: SanitizedGlobalConfig[]
i18n: Required<I18nOptions>
Expand Down
1 change: 1 addition & 0 deletions packages/payload/src/exports/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { getLocalI18n } from '../translations/getLocalI18n.js'

0 comments on commit d9bb51f

Please sign in to comment.