feat(richtext-lexical)!: rework how population works and saves data, improve node typing
This commit is contained in:
@@ -470,19 +470,24 @@ function buildObjectType({
|
||||
// is run here again, with the provided depth.
|
||||
// In the graphql find.ts resolver, the depth is then hard-coded to 0.
|
||||
// Effectively, this means that the populationPromise for GraphQL is only run here, and not in the find.ts resolver / normal population promise.
|
||||
if (editor?.populationPromise) {
|
||||
await editor?.populationPromise({
|
||||
if (editor?.populationPromises) {
|
||||
const fieldPromises = []
|
||||
const populationPromises = []
|
||||
editor?.populationPromises({
|
||||
context,
|
||||
depth,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany: false,
|
||||
flattenLocales: false,
|
||||
overrideAccess: false,
|
||||
populationPromises: [],
|
||||
populationPromises,
|
||||
req: context.req,
|
||||
showHiddenFields: false,
|
||||
siblingDoc: parent,
|
||||
})
|
||||
await Promise.all(fieldPromises)
|
||||
await Promise.all(populationPromises)
|
||||
}
|
||||
|
||||
return parent[field.name]
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { I18n } from '@payloadcms/translations'
|
||||
import type { JSONSchema4 } from 'json-schema'
|
||||
|
||||
import type { SanitizedConfig } from '../config/types.js'
|
||||
import type { Field, RichTextField, Validate } from '../fields/config/types.js'
|
||||
import type { Field, FieldBase, RichTextField, Validate } from '../fields/config/types.js'
|
||||
import type { PayloadRequest, RequestContext } from '../types/index.js'
|
||||
import type { WithServerSideProps } from './elements/WithServerSideProps.js'
|
||||
|
||||
@@ -19,15 +19,6 @@ type RichTextAdapterBase<
|
||||
AdapterProps = any,
|
||||
ExtraFieldProperties = {},
|
||||
> = {
|
||||
afterReadPromise?: ({
|
||||
field,
|
||||
incomingEditorState,
|
||||
siblingDoc,
|
||||
}: {
|
||||
field: RichTextField<Value, AdapterProps, ExtraFieldProperties>
|
||||
incomingEditorState: Value
|
||||
siblingDoc: Record<string, unknown>
|
||||
}) => Promise<void> | null
|
||||
generateComponentMap: (args: {
|
||||
WithServerSideProps: WithServerSideProps
|
||||
config: SanitizedConfig
|
||||
@@ -40,6 +31,7 @@ type RichTextAdapterBase<
|
||||
schemaMap: Map<string, Field[]>
|
||||
schemaPath: string
|
||||
}) => Map<string, Field[]>
|
||||
hooks?: FieldBase['hooks']
|
||||
outputSchema?: ({
|
||||
collectionIDFieldTypes,
|
||||
config,
|
||||
@@ -56,11 +48,18 @@ type RichTextAdapterBase<
|
||||
interfaceNameDefinitions: Map<string, JSONSchema4>
|
||||
isRequired: boolean
|
||||
}) => JSONSchema4
|
||||
populationPromise?: (data: {
|
||||
/**
|
||||
* Like an afterRead hook, but runs for both afterRead AND in the GraphQL resolver. For populating data, this should be used.
|
||||
*
|
||||
* To populate stuff / resolve field hooks, mutate the incoming populationPromises or fieldPromises array. They will then be awaited in the correct order within payload itself.
|
||||
* @param data
|
||||
*/
|
||||
populationPromises?: (data: {
|
||||
context: RequestContext
|
||||
currentDepth?: number
|
||||
depth: number
|
||||
field: RichTextField<Value, AdapterProps, ExtraFieldProperties>
|
||||
fieldPromises: Promise<void>[]
|
||||
findMany: boolean
|
||||
flattenLocales: boolean
|
||||
overrideAccess?: boolean
|
||||
@@ -68,7 +67,7 @@ type RichTextAdapterBase<
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
}) => Promise<void> | null
|
||||
}) => void
|
||||
validate: Validate<
|
||||
Value,
|
||||
Value,
|
||||
|
||||
@@ -18,6 +18,9 @@ type Args = {
|
||||
doc: Record<string, unknown>
|
||||
fallbackLocale: null | string
|
||||
field: Field | TabAsField
|
||||
/**
|
||||
* fieldPromises are used for things like field hooks. They should be awaited before awaiting populationPromises
|
||||
*/
|
||||
fieldPromises: Promise<void>[]
|
||||
findMany: boolean
|
||||
flattenLocales: boolean
|
||||
@@ -140,12 +143,13 @@ export const promise = async ({
|
||||
case 'richText': {
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
// This is run here AND in the GraphQL Resolver
|
||||
if (editor?.populationPromise) {
|
||||
const populationPromise = editor.populationPromise({
|
||||
if (editor?.populationPromises) {
|
||||
editor.populationPromises({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
@@ -154,12 +158,8 @@ export const promise = async ({
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
})
|
||||
|
||||
if (populationPromise) {
|
||||
populationPromises.push(populationPromise)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// This is only run here, independent of depth
|
||||
if (editor?.afterReadPromise) {
|
||||
const afterReadPromise = editor?.afterReadPromise({
|
||||
@@ -171,7 +171,7 @@ export const promise = async ({
|
||||
if (afterReadPromise) {
|
||||
populationPromises.push(afterReadPromise)
|
||||
}
|
||||
}
|
||||
}*/ //TODO: HOOKS!
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ type Args = {
|
||||
depth: number
|
||||
doc: Record<string, unknown>
|
||||
fallbackLocale: null | string
|
||||
/**
|
||||
* fieldPromises are used for things like field hooks. They should be awaited before awaiting populationPromises
|
||||
*/
|
||||
fieldPromises: Promise<void>[]
|
||||
fields: (Field | TabAsField)[]
|
||||
findMany: boolean
|
||||
|
||||
@@ -109,3 +109,7 @@ export type AllOperations = AuthOperations | Operation | VersionOperations
|
||||
export function docHasTimestamps(doc: any): doc is TypeWithTimestamps {
|
||||
return doc?.createdAt && doc?.updatedAt
|
||||
}
|
||||
|
||||
export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N // This is a commonly used trick to detect 'any'
|
||||
export type IsAny<T> = IfAny<T, true, false>
|
||||
export type ReplaceAny<T, DefaultType> = IsAny<T> extends true ? DefaultType : T
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import lexicalRichTextImport, { type SerializedQuoteNode } from '@lexical/rich-text'
|
||||
import lexicalRichTextImport from '@lexical/rich-text'
|
||||
const { QuoteNode } = lexicalRichTextImport
|
||||
|
||||
import type { HTMLConverter } from '../converters/html/converter/types.js'
|
||||
import type { FeatureProviderProviderServer } from '../types.js'
|
||||
|
||||
import { convertLexicalNodesToHTML } from '../converters/html/converter/index.js'
|
||||
import { createNode } from '../typeUtilities.js'
|
||||
import { BlockQuoteFeatureClientComponent } from './feature.client.js'
|
||||
import { MarkdownTransformer } from './markdownTransformer.js'
|
||||
|
||||
@@ -16,7 +16,7 @@ export const BlockQuoteFeature: FeatureProviderProviderServer<undefined, undefin
|
||||
clientFeatureProps: null,
|
||||
markdownTransformers: [MarkdownTransformer],
|
||||
nodes: [
|
||||
{
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ converters, node, parent, payload }) => {
|
||||
@@ -33,10 +33,10 @@ export const BlockQuoteFeature: FeatureProviderProviderServer<undefined, undefin
|
||||
return `<blockquote>${childrenText}</blockquote>`
|
||||
},
|
||||
nodeTypes: [QuoteNode.getType()],
|
||||
} as HTMLConverter<SerializedQuoteNode>,
|
||||
},
|
||||
},
|
||||
node: QuoteNode,
|
||||
},
|
||||
}),
|
||||
],
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
reducedBlock &&
|
||||
initialState !== false && (
|
||||
<Form
|
||||
beforeSubmit={[onChange]}
|
||||
// @ts-expect-error TODO: Fix this
|
||||
fields={fieldMap}
|
||||
initialState={initialState}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { FeatureProviderProviderServer } from '../types.js'
|
||||
import type { BlocksFeatureClientProps } from './feature.client.js'
|
||||
|
||||
import { cloneDeep } from '../../lexical/utils/cloneDeep.js'
|
||||
import { createNode } from '../typeUtilities.js'
|
||||
import { BlocksFeatureClientComponent } from './feature.client.js'
|
||||
import { BlockNode } from './nodes/BlocksNode.js'
|
||||
import { blockPopulationPromiseHOC } from './populationPromise.js'
|
||||
@@ -131,11 +132,11 @@ export const BlocksFeature: FeatureProviderProviderServer<
|
||||
},
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
createNode({
|
||||
node: BlockNode,
|
||||
populationPromises: [blockPopulationPromiseHOC(props)],
|
||||
validations: [blockValidationHOC(props)],
|
||||
},
|
||||
}),
|
||||
],
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export const blockPopulationPromiseHOC = (
|
||||
currentDepth,
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
node,
|
||||
@@ -28,8 +29,6 @@ export const blockPopulationPromiseHOC = (
|
||||
const blocks: Block[] = props.blocks
|
||||
const blockFieldData = node.fields
|
||||
|
||||
const promises: Promise<void>[] = []
|
||||
|
||||
// Sanitize block's fields here. This is done here and not in the feature, because the payload config is available here
|
||||
const payloadConfig = req.payload.config
|
||||
const validRelationships = payloadConfig.collections.map((c) => c.slug) || []
|
||||
@@ -45,7 +44,7 @@ export const blockPopulationPromiseHOC = (
|
||||
// find block used in this node
|
||||
const block = props.blocks.find((block) => block.slug === blockFieldData.blockType)
|
||||
if (!block || !block?.fields?.length || !blockFieldData) {
|
||||
return promises
|
||||
return
|
||||
}
|
||||
|
||||
recurseNestedFields({
|
||||
@@ -54,19 +53,17 @@ export const blockPopulationPromiseHOC = (
|
||||
data: blockFieldData,
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
fieldPromises,
|
||||
fields: block.fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
promises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
// The afterReadPromise gets its data from looking for field.name inside the siblingDoc. Thus, here we cannot pass the whole document's siblingDoc, but only the siblingDoc (sibling fields) of the current field.
|
||||
siblingDoc: blockFieldData,
|
||||
})
|
||||
|
||||
return promises
|
||||
}
|
||||
|
||||
return blockPopulationPromise
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { HTMLConverter } from '../converters/html/converter/types.js'
|
||||
import type { FeatureProviderProviderServer } from '../types.js'
|
||||
|
||||
import { convertLexicalNodesToHTML } from '../converters/html/converter/index.js'
|
||||
import { createNode } from '../typeUtilities.js'
|
||||
import { HeadingFeatureClientComponent } from './feature.client.js'
|
||||
import { MarkdownTransformer } from './markdownTransformer.js'
|
||||
|
||||
@@ -30,8 +31,7 @@ export const HeadingFeature: FeatureProviderProviderServer<
|
||||
ClientComponent: HeadingFeatureClientComponent,
|
||||
markdownTransformers: [MarkdownTransformer(enabledHeadingSizes)],
|
||||
nodes: [
|
||||
{
|
||||
type: HeadingNode.getType(),
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ converters, node, parent, payload }) => {
|
||||
@@ -48,10 +48,10 @@ export const HeadingFeature: FeatureProviderProviderServer<
|
||||
return '<' + node?.tag + '>' + childrenText + '</' + node?.tag + '>'
|
||||
},
|
||||
nodeTypes: [HeadingNode.getType()],
|
||||
} as HTMLConverter<SerializedHeadingNode>,
|
||||
},
|
||||
},
|
||||
node: HeadingNode,
|
||||
},
|
||||
}),
|
||||
],
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { HTMLConverter } from '../converters/html/converter/types.js'
|
||||
import type { FeatureProviderProviderServer } from '../types.js'
|
||||
import type { SerializedHorizontalRuleNode } from './nodes/HorizontalRuleNode.js'
|
||||
|
||||
import { createNode } from '../typeUtilities.js'
|
||||
import { HorizontalRuleFeatureClientComponent } from './feature.client.js'
|
||||
import { MarkdownTransformer } from './markdownTransformer.js'
|
||||
import { HorizontalRuleNode } from './nodes/HorizontalRuleNode.js'
|
||||
@@ -16,17 +15,17 @@ export const HorizontalRuleFeature: FeatureProviderProviderServer<undefined, und
|
||||
clientFeatureProps: null,
|
||||
markdownTransformers: [MarkdownTransformer],
|
||||
nodes: [
|
||||
{
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: () => {
|
||||
return `<hr/>`
|
||||
},
|
||||
nodeTypes: [HorizontalRuleNode.getType()],
|
||||
} as HTMLConverter<SerializedHorizontalRuleNode>,
|
||||
},
|
||||
},
|
||||
node: HorizontalRuleNode,
|
||||
},
|
||||
}),
|
||||
],
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ import type { SanitizedConfig } from 'payload/config'
|
||||
import type { Field, FieldWithRichTextRequiredEditor } from 'payload/types'
|
||||
|
||||
import { traverseFields } from '@payloadcms/next/utilities'
|
||||
import { deepCopyObject } from 'payload/utilities'
|
||||
|
||||
import type { HTMLConverter } from '../converters/html/converter/types.js'
|
||||
import type { FeatureProviderProviderServer } from '../types.js'
|
||||
import type { ClientProps } from './feature.client.js'
|
||||
import type { SerializedAutoLinkNode, SerializedLinkNode } from './nodes/types.js'
|
||||
|
||||
import { convertLexicalNodesToHTML } from '../converters/html/converter/index.js'
|
||||
import { createNode } from '../typeUtilities.js'
|
||||
import { LinkFeatureClientComponent } from './feature.client.js'
|
||||
import { AutoLinkNode } from './nodes/AutoLinkNode.js'
|
||||
import { LinkNode } from './nodes/LinkNode.js'
|
||||
@@ -67,21 +67,26 @@ export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps,
|
||||
enabledCollections: props.enabledCollections,
|
||||
} as ExclusiveLinkCollectionsProps,
|
||||
generateSchemaMap: ({ config, i18n, props }) => {
|
||||
if (!props?.fields || !Array.isArray(props.fields) || props.fields.length === 0) {
|
||||
return null
|
||||
}
|
||||
const schemaMap = new Map<string, Field[]>()
|
||||
|
||||
const validRelationships = config.collections.map((c) => c.slug) || []
|
||||
|
||||
const transformedFields = transformExtraFields(
|
||||
props.fields,
|
||||
deepCopyObject(props.fields),
|
||||
config,
|
||||
i18n,
|
||||
props.enabledCollections,
|
||||
props.disabledCollections,
|
||||
)
|
||||
|
||||
if (
|
||||
!transformedFields ||
|
||||
!Array.isArray(transformedFields) ||
|
||||
transformedFields.length === 0
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const schemaMap = new Map<string, Field[]>()
|
||||
|
||||
const validRelationships = config.collections.map((c) => c.slug) || []
|
||||
|
||||
schemaMap.set('fields', transformedFields)
|
||||
|
||||
traverseFields({
|
||||
@@ -96,7 +101,7 @@ export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps,
|
||||
return schemaMap
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ converters, node, parent, payload }) => {
|
||||
@@ -123,12 +128,19 @@ export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps,
|
||||
return `<a href="${href}"${rel}>${childrenText}</a>`
|
||||
},
|
||||
nodeTypes: [AutoLinkNode.getType()],
|
||||
} as HTMLConverter<SerializedAutoLinkNode>,
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
afterRead: [
|
||||
({ node }) => {
|
||||
return node
|
||||
},
|
||||
],
|
||||
},
|
||||
node: AutoLinkNode,
|
||||
populationPromises: [linkPopulationPromiseHOC(props)],
|
||||
},
|
||||
{
|
||||
}),
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ converters, node, parent, payload }) => {
|
||||
@@ -152,11 +164,11 @@ export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps,
|
||||
return `<a href="${href}"${rel}>${childrenText}</a>`
|
||||
},
|
||||
nodeTypes: [LinkNode.getType()],
|
||||
} as HTMLConverter<SerializedLinkNode>,
|
||||
},
|
||||
},
|
||||
node: LinkNode,
|
||||
populationPromises: [linkPopulationPromiseHOC(props)],
|
||||
},
|
||||
}),
|
||||
],
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { sanitizeFields } from 'payload/config'
|
||||
import { deepCopyObject } from 'payload/utilities'
|
||||
|
||||
import type { PopulationPromise } from '../types.js'
|
||||
import type { LinkFeatureServerProps } from './feature.server.js'
|
||||
import type { SerializedLinkNode } from './nodes/types.js'
|
||||
|
||||
import { populate } from '../../../populate/populate.js'
|
||||
import { recurseNestedFields } from '../../../populate/recurseNestedFields.js'
|
||||
import { transformExtraFields } from './plugins/floatingLinkEditor/utilities.js'
|
||||
|
||||
export const linkPopulationPromiseHOC = (
|
||||
props: LinkFeatureServerProps,
|
||||
): PopulationPromise<SerializedLinkNode> => {
|
||||
const linkPopulationPromise: PopulationPromise<SerializedLinkNode> = ({
|
||||
return ({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
node,
|
||||
@@ -21,53 +24,55 @@ export const linkPopulationPromiseHOC = (
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
}) => {
|
||||
const promises: Promise<void>[] = []
|
||||
// Sanitize link's fields here. This is done here and not in the feature, because the payload config is available here
|
||||
const payloadConfig = req.payload.config
|
||||
const validRelationships = payloadConfig.collections.map((c) => c.slug) || []
|
||||
|
||||
if (node?.fields?.doc?.value && node?.fields?.doc?.relationTo) {
|
||||
const collection = req.payload.collections[node?.fields?.doc?.relationTo]
|
||||
const transformedFields = transformExtraFields(
|
||||
deepCopyObject(props.fields),
|
||||
payloadConfig,
|
||||
req.i18n,
|
||||
props.enabledCollections,
|
||||
props.disabledCollections,
|
||||
)
|
||||
|
||||
if (collection) {
|
||||
promises.push(
|
||||
populate({
|
||||
id:
|
||||
typeof node?.fields?.doc?.value === 'object'
|
||||
? node?.fields?.doc?.value?.id
|
||||
: node?.fields?.doc?.value,
|
||||
collection,
|
||||
currentDepth,
|
||||
data: node?.fields?.doc,
|
||||
depth,
|
||||
field,
|
||||
key: 'value',
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}),
|
||||
)
|
||||
}
|
||||
// TODO: Sanitize & transform ahead of time! On startup!
|
||||
const sanitizedFields = sanitizeFields({
|
||||
config: payloadConfig,
|
||||
fields: transformedFields,
|
||||
requireFieldLevelRichTextEditor: true,
|
||||
validRelationships,
|
||||
})
|
||||
|
||||
if (!sanitizedFields?.length) {
|
||||
return
|
||||
}
|
||||
if (Array.isArray(props.fields)) {
|
||||
|
||||
/**
|
||||
* Should populate all fields, including the doc field (for internal links), as it's treated like a normal field
|
||||
*/
|
||||
if (Array.isArray(sanitizedFields)) {
|
||||
recurseNestedFields({
|
||||
context,
|
||||
currentDepth,
|
||||
data: node.fields || {},
|
||||
data: {
|
||||
fields: node.fields,
|
||||
},
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
fields: props.fields,
|
||||
fieldPromises,
|
||||
fields: sanitizedFields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
promises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc: node.fields || {},
|
||||
siblingDoc: {
|
||||
fields: node.fields,
|
||||
},
|
||||
})
|
||||
}
|
||||
return promises
|
||||
}
|
||||
|
||||
return linkPopulationPromise
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ const { ListItemNode, ListNode } = lexicalListImport
|
||||
|
||||
import type { FeatureProviderProviderServer } from '../../types.js'
|
||||
|
||||
import { createNode } from '../../typeUtilities.js'
|
||||
import { ListHTMLConverter, ListItemHTMLConverter } from '../htmlConverter.js'
|
||||
import { CheckListFeatureClientComponent } from './feature.client.js'
|
||||
import { CHECK_LIST } from './markdownTransformers.js'
|
||||
@@ -17,18 +18,18 @@ export const CheckListFeature: FeatureProviderProviderServer<undefined, undefine
|
||||
featureProviderMap.has('unorderedlist') || featureProviderMap.has('orderedlist')
|
||||
? []
|
||||
: [
|
||||
{
|
||||
createNode({
|
||||
converters: {
|
||||
html: ListHTMLConverter,
|
||||
},
|
||||
node: ListNode,
|
||||
},
|
||||
{
|
||||
}),
|
||||
createNode({
|
||||
converters: {
|
||||
html: ListItemHTMLConverter,
|
||||
},
|
||||
node: ListItemNode,
|
||||
},
|
||||
}),
|
||||
],
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ const { ListItemNode, ListNode } = lexicalListImport
|
||||
|
||||
import type { FeatureProviderProviderServer } from '../../types.js'
|
||||
|
||||
import { createNode } from '../../typeUtilities.js'
|
||||
import { ListHTMLConverter, ListItemHTMLConverter } from '../htmlConverter.js'
|
||||
import { OrderedListFeatureClientComponent } from './feature.client.js'
|
||||
import { ORDERED_LIST } from './markdownTransformer.js'
|
||||
@@ -16,18 +17,18 @@ export const OrderedListFeature: FeatureProviderProviderServer<undefined, undefi
|
||||
nodes: featureProviderMap.has('unorderedlist')
|
||||
? []
|
||||
: [
|
||||
{
|
||||
createNode({
|
||||
converters: {
|
||||
html: ListHTMLConverter,
|
||||
},
|
||||
node: ListNode,
|
||||
},
|
||||
{
|
||||
}),
|
||||
createNode({
|
||||
converters: {
|
||||
html: ListItemHTMLConverter,
|
||||
},
|
||||
node: ListItemNode,
|
||||
},
|
||||
}),
|
||||
],
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ const { ListItemNode, ListNode } = lexicalListImport
|
||||
|
||||
import type { FeatureProviderProviderServer } from '../../types.js'
|
||||
|
||||
import { createNode } from '../../typeUtilities.js'
|
||||
import { ListHTMLConverter, ListItemHTMLConverter } from '../htmlConverter.js'
|
||||
import { UnorderedListFeatureClientComponent } from './feature.client.js'
|
||||
import { UNORDERED_LIST } from './markdownTransformer.js'
|
||||
@@ -16,18 +17,18 @@ export const UnorderedListFeature: FeatureProviderProviderServer<undefined, unde
|
||||
ClientComponent: UnorderedListFeatureClientComponent,
|
||||
markdownTransformers: [UNORDERED_LIST],
|
||||
nodes: [
|
||||
{
|
||||
createNode({
|
||||
converters: {
|
||||
html: ListHTMLConverter,
|
||||
},
|
||||
node: ListNode,
|
||||
},
|
||||
{
|
||||
}),
|
||||
createNode({
|
||||
converters: {
|
||||
html: ListItemHTMLConverter,
|
||||
},
|
||||
node: ListItemNode,
|
||||
},
|
||||
}),
|
||||
],
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
|
||||
@@ -16,10 +16,8 @@ export const _UploadConverter: LexicalPluginNodeConverter = {
|
||||
fields,
|
||||
format: (lexicalPluginNode as any)?.format || '',
|
||||
relationTo: (lexicalPluginNode as any)?.rawImagePayload?.relationTo,
|
||||
value: {
|
||||
id: (lexicalPluginNode as any)?.rawImagePayload?.value?.id || '',
|
||||
},
|
||||
version: 1,
|
||||
value: (lexicalPluginNode as any)?.rawImagePayload?.value?.id || '',
|
||||
version: 2,
|
||||
} as const as SerializedUploadNode
|
||||
},
|
||||
nodeTypes: ['upload'],
|
||||
|
||||
@@ -7,10 +7,8 @@ export const _SlateRelationshipConverter: SlateNodeConverter = {
|
||||
type: 'relationship',
|
||||
format: '',
|
||||
relationTo: slateNode.relationTo,
|
||||
value: {
|
||||
id: slateNode?.value?.id || '',
|
||||
},
|
||||
version: 1,
|
||||
value: slateNode?.value?.id || '',
|
||||
version: 2,
|
||||
} as const as SerializedRelationshipNode
|
||||
},
|
||||
nodeTypes: ['relationship'],
|
||||
|
||||
@@ -10,10 +10,8 @@ export const _SlateUploadConverter: SlateNodeConverter = {
|
||||
},
|
||||
format: '',
|
||||
relationTo: slateNode.relationTo,
|
||||
value: {
|
||||
id: slateNode.value?.id || '',
|
||||
},
|
||||
version: 1,
|
||||
value: slateNode.value?.id || '',
|
||||
version: 2,
|
||||
} as const as SerializedUploadNode
|
||||
},
|
||||
nodeTypes: ['upload'],
|
||||
|
||||
@@ -15,28 +15,26 @@ import { EnabledRelationshipsCondition } from '../utils/EnabledRelationshipsCond
|
||||
import { INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND } from './commands.js'
|
||||
|
||||
const insertRelationship = ({
|
||||
id,
|
||||
editor,
|
||||
relationTo,
|
||||
replaceNodeKey,
|
||||
value,
|
||||
}: {
|
||||
editor: LexicalEditor
|
||||
id: string
|
||||
relationTo: string
|
||||
replaceNodeKey: null | string
|
||||
value: number | string
|
||||
}) => {
|
||||
if (!replaceNodeKey) {
|
||||
editor.dispatchCommand(INSERT_RELATIONSHIP_COMMAND, {
|
||||
relationTo,
|
||||
value: {
|
||||
id,
|
||||
},
|
||||
value,
|
||||
})
|
||||
} else {
|
||||
editor.update(() => {
|
||||
const node = $getNodeByKey(replaceNodeKey)
|
||||
if (node) {
|
||||
node.replace($createRelationshipNode({ relationTo, value: { id } }))
|
||||
node.replace($createRelationshipNode({ relationTo, value }))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -75,10 +73,10 @@ const RelationshipDrawerComponent: React.FC<Props> = ({ enabledCollectionSlugs }
|
||||
const onSelect = useCallback(
|
||||
({ collectionSlug, docID }) => {
|
||||
insertRelationship({
|
||||
id: docID,
|
||||
editor,
|
||||
relationTo: collectionSlug,
|
||||
replaceNodeKey,
|
||||
value: docID,
|
||||
})
|
||||
closeDrawer()
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { FeatureProviderProviderServer } from '../types.js'
|
||||
|
||||
import { createNode } from '../typeUtilities.js'
|
||||
import { RelationshipFeatureClientComponent } from './feature.client.js'
|
||||
import { RelationshipNode } from './nodes/RelationshipNode.js'
|
||||
import { relationshipPopulationPromise } from './populationPromise.js'
|
||||
@@ -36,11 +37,11 @@ export const RelationshipFeature: FeatureProviderProviderServer<
|
||||
ClientComponent: RelationshipFeatureClientComponent,
|
||||
clientFeatureProps: props,
|
||||
nodes: [
|
||||
{
|
||||
createNode({
|
||||
node: RelationshipNode,
|
||||
populationPromises: [relationshipPopulationPromise],
|
||||
// TODO: Add validation similar to upload
|
||||
},
|
||||
}),
|
||||
],
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
|
||||
@@ -25,11 +25,7 @@ const RelationshipComponent = React.lazy(() =>
|
||||
|
||||
export type RelationshipData = {
|
||||
relationTo: string
|
||||
value: {
|
||||
// Actual relationship, populated in afterRead hook
|
||||
[key: string]: unknown
|
||||
id: string
|
||||
}
|
||||
value: number | string
|
||||
}
|
||||
|
||||
export type SerializedRelationshipNode = Spread<RelationshipData, SerializedDecoratorBlockNode>
|
||||
@@ -41,9 +37,7 @@ function relationshipElementToNode(domNode: HTMLDivElement): DOMConversionOutput
|
||||
if (id != null && relationTo != null) {
|
||||
const node = $createRelationshipNode({
|
||||
relationTo,
|
||||
value: {
|
||||
id,
|
||||
},
|
||||
value: id,
|
||||
})
|
||||
return { node }
|
||||
}
|
||||
@@ -96,6 +90,10 @@ export class RelationshipNode extends DecoratorBlockNode {
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedRelationshipNode): RelationshipNode {
|
||||
if (serializedNode.version === 1 && (serializedNode?.value as unknown as { id: string })?.id) {
|
||||
serializedNode.value = (serializedNode.value as unknown as { id: string }).id
|
||||
}
|
||||
|
||||
const importedData: RelationshipData = {
|
||||
relationTo: serializedNode.relationTo,
|
||||
value: serializedNode.value,
|
||||
@@ -108,6 +106,7 @@ export class RelationshipNode extends DecoratorBlockNode {
|
||||
static isInline(): false {
|
||||
return false
|
||||
}
|
||||
|
||||
decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element {
|
||||
return (
|
||||
<RelationshipComponent
|
||||
@@ -118,10 +117,9 @@ export class RelationshipNode extends DecoratorBlockNode {
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
exportDOM(): DOMExportOutput {
|
||||
const element = document.createElement('div')
|
||||
element.setAttribute('data-lexical-relationship-id', this.__data?.value?.id)
|
||||
element.setAttribute('data-lexical-relationship-id', String(this.__data?.value))
|
||||
element.setAttribute('data-lexical-relationship-relationTo', this.__data?.relationTo)
|
||||
|
||||
const text = document.createTextNode(this.getTextContent())
|
||||
@@ -134,7 +132,7 @@ export class RelationshipNode extends DecoratorBlockNode {
|
||||
...super.exportJSON(),
|
||||
...this.getData(),
|
||||
type: this.getType(),
|
||||
version: 1,
|
||||
version: 2,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +141,7 @@ export class RelationshipNode extends DecoratorBlockNode {
|
||||
}
|
||||
|
||||
getTextContent(): string {
|
||||
return `${this.__data?.relationTo} relation to ${this.__data?.value?.id}`
|
||||
return `${this.__data?.relationTo} relation to ${this.__data?.value}`
|
||||
}
|
||||
|
||||
setData(data: RelationshipData): void {
|
||||
|
||||
@@ -38,10 +38,7 @@ type Props = {
|
||||
const Component: React.FC<Props> = (props) => {
|
||||
const {
|
||||
children,
|
||||
data: {
|
||||
relationTo,
|
||||
value: { id },
|
||||
},
|
||||
data: { relationTo, value: id },
|
||||
nodeKey,
|
||||
} = props
|
||||
|
||||
|
||||
@@ -9,18 +9,20 @@ export const relationshipPopulationPromise: PopulationPromise<SerializedRelation
|
||||
field,
|
||||
node,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}) => {
|
||||
const promises: Promise<void>[] = []
|
||||
if (node?.value) {
|
||||
// @ts-expect-error
|
||||
const id = node?.value?.id || node?.value // for backwards-compatibility
|
||||
|
||||
if (node?.value?.id) {
|
||||
const collection = req.payload.collections[node?.relationTo]
|
||||
|
||||
if (collection) {
|
||||
promises.push(
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id: node.value.id,
|
||||
id,
|
||||
collection,
|
||||
currentDepth,
|
||||
data: node,
|
||||
@@ -34,6 +36,4 @@ export const relationshipPopulationPromise: PopulationPromise<SerializedRelation
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return promises
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { LexicalNode } from 'lexical'
|
||||
|
||||
import type { NodeWithHooks } from './types.js'
|
||||
|
||||
/**
|
||||
* Utility function to create a node with hooks. You don't have to use this utility, but it improves type inference
|
||||
* @param node the node
|
||||
*/
|
||||
export function createNode<Node extends LexicalNode>(
|
||||
node: NodeWithHooks<Node>,
|
||||
): NodeWithHooks<Node> {
|
||||
return node
|
||||
}
|
||||
@@ -6,7 +6,13 @@ import type { SerializedLexicalNode } from 'lexical'
|
||||
import type { LexicalNodeReplacement } from 'lexical'
|
||||
import type { RequestContext } from 'payload'
|
||||
import type { SanitizedConfig } from 'payload/config'
|
||||
import type { Field, PayloadRequest, RichTextField, ValidateOptions } from 'payload/types'
|
||||
import type {
|
||||
Field,
|
||||
PayloadRequest,
|
||||
ReplaceAny,
|
||||
RichTextField,
|
||||
ValidateOptions,
|
||||
} from 'payload/types'
|
||||
import type React from 'react'
|
||||
|
||||
import type { AdapterProps } from '../../types.js'
|
||||
@@ -21,6 +27,7 @@ export type PopulationPromise<T extends SerializedLexicalNode = SerializedLexica
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
node,
|
||||
@@ -38,6 +45,10 @@ export type PopulationPromise<T extends SerializedLexicalNode = SerializedLexica
|
||||
*/
|
||||
editorPopulationPromises: Map<string, Array<PopulationPromise>>
|
||||
field: RichTextField<SerializedEditorState, AdapterProps>
|
||||
/**
|
||||
* fieldPromises are used for things like field hooks. They will be awaited before awaiting populationPromises
|
||||
*/
|
||||
fieldPromises: Promise<void>[]
|
||||
findMany: boolean
|
||||
flattenLocales: boolean
|
||||
node: T
|
||||
@@ -46,7 +57,7 @@ export type PopulationPromise<T extends SerializedLexicalNode = SerializedLexica
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
}) => Promise<void>[]
|
||||
}) => void
|
||||
|
||||
export type NodeValidation<T extends SerializedLexicalNode = SerializedLexicalNode> = ({
|
||||
node,
|
||||
@@ -171,6 +182,44 @@ export type ClientComponentProps<ClientFeatureProps> = ClientFeatureProps & {
|
||||
order: number
|
||||
}
|
||||
|
||||
export type FieldNodeHookArgs<T extends SerializedLexicalNode> = {
|
||||
context: RequestContext
|
||||
/** Boolean to denote if this hook is running against finding one, or finding many within the afterRead hook. */
|
||||
findMany?: boolean
|
||||
/** The value of the field. */
|
||||
node?: T
|
||||
/** A string relating to which operation the field type is currently executing within. Useful within beforeValidate, beforeChange, and afterChange hooks to differentiate between create and update operations. */
|
||||
operation?: 'create' | 'delete' | 'read' | 'update'
|
||||
/** The Express request object. It is mocked for Local API operations. */
|
||||
req: PayloadRequest
|
||||
}
|
||||
|
||||
export type FieldNodeHook<T extends SerializedLexicalNode> = (
|
||||
args: FieldNodeHookArgs<T>,
|
||||
) => Promise<T> | T
|
||||
|
||||
// Define the node with hooks that use the node's exportJSON return type
|
||||
export type NodeWithHooks<T extends LexicalNode = any> = {
|
||||
converters?: {
|
||||
html?: HTMLConverter<ReturnType<ReplaceAny<T, LexicalNode>['exportJSON']>>
|
||||
}
|
||||
hooks?: {
|
||||
afterChange?: Array<FieldNodeHook<ReturnType<ReplaceAny<T, LexicalNode>['exportJSON']>>>
|
||||
afterRead?: Array<FieldNodeHook<ReturnType<ReplaceAny<T, LexicalNode>['exportJSON']>>>
|
||||
beforeChange?: Array<FieldNodeHook<ReturnType<ReplaceAny<T, LexicalNode>['exportJSON']>>>
|
||||
/**
|
||||
* Runs before a document is duplicated to prevent errors in unique fields or return null to use defaultValue.
|
||||
*/
|
||||
beforeDuplicate?: Array<FieldNodeHook<ReturnType<ReplaceAny<T, LexicalNode>['exportJSON']>>>
|
||||
beforeValidate?: Array<FieldNodeHook<ReturnType<ReplaceAny<T, LexicalNode>['exportJSON']>>>
|
||||
}
|
||||
node: Klass<T> | LexicalNodeReplacement
|
||||
populationPromises?: Array<
|
||||
PopulationPromise<ReturnType<ReplaceAny<T, LexicalNode>['exportJSON']>>
|
||||
>
|
||||
validations?: Array<NodeValidation<ReturnType<ReplaceAny<T, LexicalNode>['exportJSON']>>>
|
||||
}
|
||||
|
||||
export type ServerFeature<ServerProps, ClientFeatureProps> = {
|
||||
ClientComponent?: React.FC<ClientComponentProps<ClientFeatureProps>>
|
||||
/**
|
||||
@@ -215,26 +264,8 @@ export type ServerFeature<ServerProps, ClientFeatureProps> = {
|
||||
isRequired: boolean
|
||||
}) => JSONSchema4
|
||||
}
|
||||
hooks?: {
|
||||
afterReadPromise?: ({
|
||||
field,
|
||||
incomingEditorState,
|
||||
siblingDoc,
|
||||
}: {
|
||||
field: RichTextField<SerializedEditorState, AdapterProps>
|
||||
incomingEditorState: SerializedEditorState
|
||||
siblingDoc: Record<string, unknown>
|
||||
}) => Promise<void> | null
|
||||
}
|
||||
markdownTransformers?: Transformer[]
|
||||
nodes?: Array<{
|
||||
converters?: {
|
||||
html?: HTMLConverter
|
||||
}
|
||||
node: Klass<LexicalNode> | LexicalNodeReplacement
|
||||
populationPromises?: Array<PopulationPromise>
|
||||
validations?: Array<NodeValidation>
|
||||
}>
|
||||
nodes?: Array<NodeWithHooks>
|
||||
|
||||
/** Props which were passed into your feature will have to be passed here. This will allow them to be used / read in other places of the code, e.g. wherever you can use useEditorConfigContext */
|
||||
serverFeatureProps: ServerProps
|
||||
@@ -325,20 +356,18 @@ export type SanitizedServerFeatures = Required<
|
||||
}) => JSONSchema4
|
||||
>
|
||||
}
|
||||
hooks: {
|
||||
afterReadPromises: Array<
|
||||
({
|
||||
field,
|
||||
incomingEditorState,
|
||||
siblingDoc,
|
||||
}: {
|
||||
field: RichTextField<SerializedEditorState, AdapterProps>
|
||||
incomingEditorState: SerializedEditorState
|
||||
siblingDoc: Record<string, unknown>
|
||||
}) => Promise<void> | null
|
||||
>
|
||||
}
|
||||
/** The node types mapped to their populationPromises */
|
||||
/** The node types mapped to their hooks */
|
||||
|
||||
hooks?: {
|
||||
afterChange?: Map<string, Array<FieldNodeHook<SerializedLexicalNode>>>
|
||||
afterRead?: Map<string, Array<FieldNodeHook<SerializedLexicalNode>>>
|
||||
beforeChange?: Map<string, Array<FieldNodeHook<SerializedLexicalNode>>>
|
||||
/**
|
||||
* Runs before a document is duplicated to prevent errors in unique fields or return null to use defaultValue.
|
||||
*/
|
||||
beforeDuplicate?: Map<string, Array<FieldNodeHook<SerializedLexicalNode>>>
|
||||
beforeValidate?: Map<string, Array<FieldNodeHook<SerializedLexicalNode>>>
|
||||
} /** The node types mapped to their populationPromises */
|
||||
populationPromises: Map<string, Array<PopulationPromise>>
|
||||
/** The node types mapped to their validations */
|
||||
validations: Map<string, Array<NodeValidation>>
|
||||
|
||||
@@ -65,13 +65,13 @@ const Component: React.FC<ElementProps> = (props) => {
|
||||
const drawerSlug = useDrawerSlug('upload-drawer')
|
||||
|
||||
const [DocumentDrawer, DocumentDrawerToggler, { closeDrawer }] = useDocumentDrawer({
|
||||
id: value?.id,
|
||||
id: value,
|
||||
collectionSlug: relatedCollection.slug,
|
||||
})
|
||||
|
||||
// Get the referenced document
|
||||
const [{ data }, { setParams }] = usePayloadAPI(
|
||||
`${serverURL}${api}/${relatedCollection.slug}/${value?.id}`,
|
||||
`${serverURL}${api}/${relatedCollection.slug}/${value}`,
|
||||
{ initialParams },
|
||||
)
|
||||
|
||||
@@ -172,7 +172,7 @@ const Component: React.FC<ElementProps> = (props) => {
|
||||
</DocumentDrawerToggler>
|
||||
</div>
|
||||
</div>
|
||||
{value?.id && <DocumentDrawer onSave={updateUpload} />}
|
||||
{value && <DocumentDrawer onSave={updateUpload} />}
|
||||
{hasExtraFields ? (
|
||||
<ExtraFieldsUploadDrawer
|
||||
drawerSlug={drawerSlug}
|
||||
|
||||
@@ -17,21 +17,21 @@ import { INSERT_UPLOAD_WITH_DRAWER_COMMAND } from './commands.js'
|
||||
const baseClass = 'lexical-upload-drawer'
|
||||
|
||||
const insertUpload = ({
|
||||
id,
|
||||
editor,
|
||||
relationTo,
|
||||
replaceNodeKey,
|
||||
value,
|
||||
}: {
|
||||
editor: LexicalEditor
|
||||
id: string
|
||||
relationTo: string
|
||||
replaceNodeKey: null | string
|
||||
value: number | string
|
||||
}) => {
|
||||
if (!replaceNodeKey) {
|
||||
editor.dispatchCommand(INSERT_UPLOAD_COMMAND, {
|
||||
id,
|
||||
fields: null,
|
||||
relationTo,
|
||||
value,
|
||||
})
|
||||
} else {
|
||||
editor.update(() => {
|
||||
@@ -42,9 +42,7 @@ const insertUpload = ({
|
||||
data: {
|
||||
fields: null,
|
||||
relationTo,
|
||||
value: {
|
||||
id,
|
||||
},
|
||||
value,
|
||||
},
|
||||
}),
|
||||
)
|
||||
@@ -84,10 +82,10 @@ const UploadDrawerComponent: React.FC<Props> = ({ enabledCollectionSlugs }) => {
|
||||
const onSelect = useCallback(
|
||||
({ collectionSlug, docID }) => {
|
||||
insertUpload({
|
||||
id: docID,
|
||||
editor,
|
||||
relationTo: collectionSlug,
|
||||
replaceNodeKey,
|
||||
value: docID,
|
||||
})
|
||||
closeDrawer()
|
||||
},
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import type { Field, FieldWithRichTextRequiredEditor, Payload } from 'payload/types'
|
||||
import type {
|
||||
Field,
|
||||
FieldWithRichTextRequiredEditor,
|
||||
FileData,
|
||||
FileSize,
|
||||
Payload,
|
||||
TypeWithID,
|
||||
} from 'payload/types'
|
||||
|
||||
import { traverseFields } from '@payloadcms/next/utilities'
|
||||
|
||||
import type { HTMLConverter } from '../converters/html/converter/types.js'
|
||||
import type { FeatureProviderProviderServer } from '../types.js'
|
||||
import type { UploadFeaturePropsClient } from './feature.client.js'
|
||||
|
||||
import { createNode } from '../typeUtilities.js'
|
||||
import { UploadFeatureClientComponent } from './feature.client.js'
|
||||
import { type SerializedUploadNode, UploadNode } from './nodes/UploadNode.js'
|
||||
import { UploadNode } from './nodes/UploadNode.js'
|
||||
import { uploadPopulationPromiseHOC } from './populationPromise.js'
|
||||
import { uploadValidation } from './validate.js'
|
||||
|
||||
@@ -71,17 +78,21 @@ export const UploadFeature: FeatureProviderProviderServer<
|
||||
return schemaMap
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ node, payload }) => {
|
||||
// @ts-expect-error
|
||||
const id = node?.value?.id || node?.value // for backwards-compatibility
|
||||
|
||||
if (payload) {
|
||||
let uploadDocument: any
|
||||
let uploadDocument: TypeWithID & FileData
|
||||
|
||||
try {
|
||||
uploadDocument = await payload.findByID({
|
||||
id: node.value.id,
|
||||
uploadDocument = (await payload.findByID({
|
||||
id,
|
||||
collection: node.relationTo,
|
||||
})
|
||||
})) as TypeWithID & FileData
|
||||
} catch (ignored) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
@@ -93,12 +104,12 @@ export const UploadFeature: FeatureProviderProviderServer<
|
||||
return `<img />`
|
||||
}
|
||||
|
||||
const url: string = getAbsoluteURL(uploadDocument?.url as string, payload)
|
||||
const url = getAbsoluteURL(uploadDocument?.url, payload)
|
||||
|
||||
/**
|
||||
* If the upload is not an image, return a link to the upload
|
||||
*/
|
||||
if (!(uploadDocument?.mimeType as string)?.startsWith('image')) {
|
||||
if (!uploadDocument?.mimeType?.startsWith('image')) {
|
||||
return `<a href="${url}" rel="noopener noreferrer">${uploadDocument.filename}</a>`
|
||||
}
|
||||
|
||||
@@ -116,7 +127,9 @@ export const UploadFeature: FeatureProviderProviderServer<
|
||||
|
||||
// Iterate through each size in the data.sizes object
|
||||
for (const size in uploadDocument.sizes) {
|
||||
const imageSize = uploadDocument.sizes[size]
|
||||
const imageSize: FileSize & {
|
||||
url?: string
|
||||
} = uploadDocument.sizes[size]
|
||||
|
||||
// Skip if any property of the size object is null
|
||||
if (
|
||||
@@ -129,7 +142,7 @@ export const UploadFeature: FeatureProviderProviderServer<
|
||||
) {
|
||||
continue
|
||||
}
|
||||
const imageSizeURL: string = getAbsoluteURL(imageSize?.url as string, payload)
|
||||
const imageSizeURL = getAbsoluteURL(imageSize?.url, payload)
|
||||
|
||||
pictureHTML += `<source srcset="${imageSizeURL}" media="(max-width: ${imageSize.width}px)" type="${imageSize.mimeType}">`
|
||||
}
|
||||
@@ -139,16 +152,16 @@ export const UploadFeature: FeatureProviderProviderServer<
|
||||
pictureHTML += '</picture>'
|
||||
return pictureHTML
|
||||
} else {
|
||||
return `<img src="${node.value.id}" />`
|
||||
return `<img src="${id}" />`
|
||||
}
|
||||
},
|
||||
nodeTypes: [UploadNode.getType()],
|
||||
} as HTMLConverter<SerializedUploadNode>,
|
||||
},
|
||||
},
|
||||
node: UploadNode,
|
||||
populationPromises: [uploadPopulationPromiseHOC(props)],
|
||||
validations: [uploadValidation()],
|
||||
},
|
||||
}),
|
||||
],
|
||||
serverFeatureProps: props,
|
||||
}
|
||||
|
||||
@@ -21,26 +21,13 @@ const RawUploadComponent = React.lazy(() =>
|
||||
import('../component/index.js').then((module) => ({ default: module.UploadComponent })),
|
||||
)
|
||||
|
||||
export type RawUploadPayload = {
|
||||
fields: {
|
||||
// unknown, custom fields:
|
||||
[key: string]: unknown
|
||||
}
|
||||
id: string
|
||||
relationTo: string
|
||||
}
|
||||
|
||||
export type UploadData = {
|
||||
fields: {
|
||||
// unknown, custom fields:
|
||||
[key: string]: unknown
|
||||
}
|
||||
relationTo: string
|
||||
value: {
|
||||
// Actual upload data, populated in afterRead hook
|
||||
[key: string]: unknown
|
||||
id: string
|
||||
}
|
||||
value: number | string
|
||||
}
|
||||
|
||||
function convertUploadElement(domNode: Node): DOMConversionOutput | null {
|
||||
@@ -93,6 +80,10 @@ export class UploadNode extends DecoratorBlockNode {
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedUploadNode): UploadNode {
|
||||
if (serializedNode.version === 1 && (serializedNode?.value as unknown as { id: string })?.id) {
|
||||
serializedNode.value = (serializedNode.value as unknown as { id: string }).id
|
||||
}
|
||||
|
||||
const importedData: UploadData = {
|
||||
fields: serializedNode.fields,
|
||||
relationTo: serializedNode.relationTo,
|
||||
@@ -126,7 +117,7 @@ export class UploadNode extends DecoratorBlockNode {
|
||||
...super.exportJSON(),
|
||||
...this.getData(),
|
||||
type: this.getType(),
|
||||
version: 1,
|
||||
version: 2,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@ import type { LexicalCommand } from 'lexical'
|
||||
import { useConfig } from '@payloadcms/ui/providers/Config'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import type { RawUploadPayload } from '../nodes/UploadNode.js'
|
||||
import type { UploadData } from '../nodes/UploadNode.js'
|
||||
|
||||
import { UploadDrawer } from '../drawer/index.js'
|
||||
import { $createUploadNode, UploadNode } from '../nodes/UploadNode.js'
|
||||
|
||||
export type InsertUploadPayload = Readonly<RawUploadPayload>
|
||||
export type InsertUploadPayload = Readonly<UploadData>
|
||||
|
||||
export const INSERT_UPLOAD_COMMAND: LexicalCommand<InsertUploadPayload> =
|
||||
createCommand('INSERT_UPLOAD_COMMAND')
|
||||
@@ -46,9 +46,7 @@ export function UploadPlugin(): JSX.Element | null {
|
||||
data: {
|
||||
fields: payload.fields,
|
||||
relationTo: payload.relationTo,
|
||||
value: {
|
||||
id: payload.id,
|
||||
},
|
||||
value: payload.value,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { sanitizeFields } from 'payload/config'
|
||||
|
||||
import type { PopulationPromise } from '../types.js'
|
||||
import type { UploadFeatureProps } from './feature.server.js'
|
||||
import type { SerializedUploadNode } from './nodes/UploadNode.js'
|
||||
@@ -8,12 +10,13 @@ import { recurseNestedFields } from '../../../populate/recurseNestedFields.js'
|
||||
export const uploadPopulationPromiseHOC = (
|
||||
props?: UploadFeatureProps,
|
||||
): PopulationPromise<SerializedUploadNode> => {
|
||||
const uploadPopulationPromise: PopulationPromise<SerializedUploadNode> = ({
|
||||
return ({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
node,
|
||||
@@ -21,17 +24,19 @@ export const uploadPopulationPromiseHOC = (
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
}) => {
|
||||
const promises: Promise<void>[] = []
|
||||
const payloadConfig = req.payload.config
|
||||
|
||||
if (node?.value?.id) {
|
||||
if (node?.value) {
|
||||
const collection = req.payload.collections[node?.relationTo]
|
||||
|
||||
if (collection) {
|
||||
promises.push(
|
||||
// @ts-expect-error
|
||||
const id = node?.value?.id || node?.value // for backwards-compatibility
|
||||
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id: node?.value?.id,
|
||||
id,
|
||||
collection,
|
||||
currentDepth,
|
||||
data: node,
|
||||
@@ -45,27 +50,36 @@ export const uploadPopulationPromiseHOC = (
|
||||
)
|
||||
}
|
||||
if (Array.isArray(props?.collections?.[node?.relationTo]?.fields)) {
|
||||
const validRelationships = payloadConfig.collections.map((c) => c.slug) || []
|
||||
|
||||
// TODO: Sanitize & transform ahead of time! On startup!
|
||||
const sanitizedFields = sanitizeFields({
|
||||
config: payloadConfig,
|
||||
fields: props?.collections?.[node?.relationTo]?.fields,
|
||||
requireFieldLevelRichTextEditor: true,
|
||||
validRelationships,
|
||||
})
|
||||
|
||||
if (!sanitizedFields?.length) {
|
||||
return
|
||||
}
|
||||
recurseNestedFields({
|
||||
context,
|
||||
currentDepth,
|
||||
data: node.fields || {},
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
fields: props?.collections?.[node?.relationTo]?.fields,
|
||||
fieldPromises,
|
||||
fields: sanitizedFields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
promises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc: node.fields || {},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return promises
|
||||
}
|
||||
|
||||
return uploadPopulationPromise
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { SerializedUploadNode } from './nodes/UploadNode.js'
|
||||
import { CAN_USE_DOM } from '../../lexical/utils/canUseDOM.js'
|
||||
|
||||
export const uploadValidation = (): NodeValidation<SerializedUploadNode> => {
|
||||
const uploadValidation: NodeValidation<SerializedUploadNode> = ({
|
||||
return ({
|
||||
node,
|
||||
validation: {
|
||||
options: {
|
||||
@@ -16,8 +16,10 @@ export const uploadValidation = (): NodeValidation<SerializedUploadNode> => {
|
||||
}) => {
|
||||
if (!CAN_USE_DOM) {
|
||||
const idType = payload.collections[node.relationTo].customIDType || payload.db.defaultIDType
|
||||
// @ts-expect-error
|
||||
const id = node?.value?.id || node?.value // for backwards-compatibility
|
||||
|
||||
if (!isValidID(node.value?.id, idType)) {
|
||||
if (!isValidID(id, idType)) {
|
||||
return t('validation:validUploadID')
|
||||
}
|
||||
}
|
||||
@@ -26,6 +28,4 @@ export const uploadValidation = (): NodeValidation<SerializedUploadNode> => {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return uploadValidation
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@ export const sanitizeServerFeatures = (
|
||||
modifyOutputSchemas: [],
|
||||
},
|
||||
hooks: {
|
||||
afterReadPromises: [],
|
||||
afterChange: new Map(),
|
||||
afterRead: new Map(),
|
||||
beforeChange: new Map(),
|
||||
beforeDuplicate: new Map(),
|
||||
beforeValidate: new Map(),
|
||||
},
|
||||
markdownTransformers: [],
|
||||
nodes: [],
|
||||
@@ -33,13 +37,6 @@ export const sanitizeServerFeatures = (
|
||||
if (feature?.generatedTypes?.modifyOutputSchema) {
|
||||
sanitized.generatedTypes.modifyOutputSchemas.push(feature.generatedTypes.modifyOutputSchema)
|
||||
}
|
||||
if (feature.hooks) {
|
||||
if (feature.hooks.afterReadPromise) {
|
||||
sanitized.hooks.afterReadPromises = sanitized.hooks.afterReadPromises.concat(
|
||||
feature.hooks.afterReadPromise,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (feature.nodes?.length) {
|
||||
sanitized.nodes = sanitized.nodes.concat(feature.nodes)
|
||||
@@ -54,6 +51,21 @@ export const sanitizeServerFeatures = (
|
||||
if (node?.converters?.html) {
|
||||
sanitized.converters.html.push(node.converters.html)
|
||||
}
|
||||
if (node?.hooks?.afterChange) {
|
||||
sanitized.hooks.afterChange.set(nodeType, node.hooks.afterChange)
|
||||
}
|
||||
if (node?.hooks?.afterRead) {
|
||||
sanitized.hooks.afterRead.set(nodeType, node.hooks.afterRead)
|
||||
}
|
||||
if (node?.hooks?.beforeChange) {
|
||||
sanitized.hooks.beforeChange.set(nodeType, node.hooks.beforeChange)
|
||||
}
|
||||
if (node?.hooks?.beforeDuplicate) {
|
||||
sanitized.hooks.beforeDuplicate.set(nodeType, node.hooks.beforeDuplicate)
|
||||
}
|
||||
if (node?.hooks?.beforeValidate) {
|
||||
sanitized.hooks.beforeValidate.set(nodeType, node.hooks.beforeValidate)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ export function validateUrl(url: string): boolean {
|
||||
// TODO Fix UI for link insertion; it should never default to an invalid URL such as https://.
|
||||
// Maybe show a dialog where they user can type the URL before inserting it.
|
||||
|
||||
if (!url) return false
|
||||
|
||||
if (url === 'https://') return true
|
||||
|
||||
// This makes sure URLs starting with www. instead of https are valid too
|
||||
|
||||
@@ -20,7 +20,7 @@ import { sanitizeServerFeatures } from './field/lexical/config/server/sanitize.j
|
||||
import { cloneDeep } from './field/lexical/utils/cloneDeep.js'
|
||||
import { getGenerateComponentMap } from './generateComponentMap.js'
|
||||
import { getGenerateSchemaMap } from './generateSchemaMap.js'
|
||||
import { richTextRelationshipPromise } from './populate/richTextRelationshipPromise.js'
|
||||
import { populateLexicalPopulationPromises } from './populate/populateLexicalPopulationPromises.js'
|
||||
import { richTextValidateHOC } from './validate/index.js'
|
||||
|
||||
export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapter {
|
||||
@@ -64,29 +64,6 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
Component: RichTextField,
|
||||
toMergeIntoProps: { lexicalEditorConfig: finalSanitizedEditorConfig.lexical },
|
||||
}),
|
||||
afterReadPromise: ({ field, incomingEditorState, siblingDoc }) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const promises: Promise<void>[] = []
|
||||
|
||||
if (finalSanitizedEditorConfig?.features?.hooks?.afterReadPromises?.length) {
|
||||
for (const afterReadPromise of finalSanitizedEditorConfig.features.hooks
|
||||
.afterReadPromises) {
|
||||
const promise = afterReadPromise({
|
||||
field,
|
||||
incomingEditorState,
|
||||
siblingDoc,
|
||||
})
|
||||
if (promise) {
|
||||
promises.push(promise)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(promises)
|
||||
.then(() => resolve())
|
||||
.catch((error) => reject(error))
|
||||
})
|
||||
},
|
||||
editorConfig: finalSanitizedEditorConfig,
|
||||
generateComponentMap: getGenerateComponentMap({
|
||||
resolvedFeatureMap,
|
||||
@@ -94,6 +71,13 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
generateSchemaMap: getGenerateSchemaMap({
|
||||
resolvedFeatureMap,
|
||||
}),
|
||||
/* hooks: {
|
||||
afterChange: finalSanitizedEditorConfig.features.hooks.afterChange,
|
||||
afterRead: finalSanitizedEditorConfig.features.hooks.afterRead,
|
||||
beforeChange: finalSanitizedEditorConfig.features.hooks.beforeChange,
|
||||
beforeDuplicate: finalSanitizedEditorConfig.features.hooks.beforeDuplicate,
|
||||
beforeValidate: finalSanitizedEditorConfig.features.hooks.beforeValidate,
|
||||
},*/
|
||||
outputSchema: ({
|
||||
collectionIDFieldTypes,
|
||||
config,
|
||||
@@ -171,11 +155,12 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
|
||||
return outputSchema
|
||||
},
|
||||
populationPromise({
|
||||
populationPromises({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
@@ -186,12 +171,13 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
}) {
|
||||
// check if there are any features with nodes which have populationPromises for this field
|
||||
if (finalSanitizedEditorConfig?.features?.populationPromises?.size) {
|
||||
return richTextRelationshipPromise({
|
||||
populateLexicalPopulationPromises({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
editorPopulationPromises: finalSanitizedEditorConfig.features.populationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
@@ -201,8 +187,6 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
siblingDoc,
|
||||
})
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
validate: richTextValidateHOC({
|
||||
editorConfig: finalSanitizedEditorConfig,
|
||||
@@ -311,14 +295,19 @@ export {
|
||||
RelationshipNode,
|
||||
type SerializedRelationshipNode,
|
||||
} from './field/features/relationship/nodes/RelationshipNode.js'
|
||||
export { createNode } from './field/features/typeUtilities.js'
|
||||
export type {
|
||||
ClientComponentProps,
|
||||
ClientFeature,
|
||||
ClientFeatureProviderMap,
|
||||
FeatureProviderClient,
|
||||
FeatureProviderProviderClient,
|
||||
FeatureProviderProviderServer,
|
||||
FeatureProviderServer,
|
||||
FieldNodeHook,
|
||||
FieldNodeHookArgs,
|
||||
NodeValidation,
|
||||
NodeWithHooks,
|
||||
PopulationPromise,
|
||||
ResolvedClientFeature,
|
||||
ResolvedClientFeatureMap,
|
||||
@@ -334,8 +323,6 @@ export { UploadFeature } from './field/features/upload/feature.server.js'
|
||||
|
||||
export type { UploadFeatureProps } from './field/features/upload/feature.server.js'
|
||||
|
||||
export type { RawUploadPayload } from './field/features/upload/nodes/UploadNode.js'
|
||||
|
||||
export {
|
||||
$createUploadNode,
|
||||
$isUploadNode,
|
||||
|
||||
@@ -28,7 +28,7 @@ export const populate = async ({
|
||||
}: Omit<Arguments, 'field'> & {
|
||||
collection: Collection
|
||||
field: Field
|
||||
id: string
|
||||
id: number | string
|
||||
}): Promise<void> => {
|
||||
const dataRef = data as Record<string, unknown>
|
||||
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
import type { SerializedEditorState, SerializedLexicalNode } from 'lexical'
|
||||
import type { PayloadRequest, RichTextAdapter, RichTextField } from 'payload/types'
|
||||
import type { RichTextAdapter } from 'payload/types'
|
||||
|
||||
import type { PopulationPromise } from '../field/features/types.js'
|
||||
import type { AdapterProps } from '../types.js'
|
||||
|
||||
export type Args = Parameters<
|
||||
RichTextAdapter<SerializedEditorState, AdapterProps>['populationPromise']
|
||||
RichTextAdapter<SerializedEditorState, AdapterProps>['populationPromises']
|
||||
>[0] & {
|
||||
editorPopulationPromises: Map<string, Array<PopulationPromise>>
|
||||
}
|
||||
|
||||
type RecurseRichTextArgs = {
|
||||
children: SerializedLexicalNode[]
|
||||
currentDepth: number
|
||||
depth: number
|
||||
editorPopulationPromises: Map<string, Array<PopulationPromise>>
|
||||
field: RichTextField<SerializedEditorState, AdapterProps>
|
||||
overrideAccess: boolean
|
||||
promises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const recurseRichText = ({
|
||||
@@ -30,15 +21,15 @@ export const recurseRichText = ({
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess = false,
|
||||
populationPromises,
|
||||
promises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
}: RecurseRichTextArgs & Args): void => {
|
||||
}: Args & RecurseRichTextArgs): void => {
|
||||
if (depth <= 0 || currentDepth > depth) {
|
||||
return
|
||||
}
|
||||
@@ -47,23 +38,22 @@ export const recurseRichText = ({
|
||||
children.forEach((node) => {
|
||||
if (editorPopulationPromises?.has(node.type)) {
|
||||
for (const promise of editorPopulationPromises.get(node.type)) {
|
||||
promises.push(
|
||||
...promise({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
node,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
}),
|
||||
)
|
||||
promise({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
node,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,11 +65,11 @@ export const recurseRichText = ({
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
promises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
@@ -89,12 +79,16 @@ export const recurseRichText = ({
|
||||
}
|
||||
}
|
||||
|
||||
export const richTextRelationshipPromise = async ({
|
||||
/**
|
||||
* Appends all new populationPromises to the populationPromises prop
|
||||
*/
|
||||
export const populateLexicalPopulationPromises = ({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
@@ -102,9 +96,7 @@ export const richTextRelationshipPromise = async ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
}: Args): Promise<void> => {
|
||||
const promises = []
|
||||
|
||||
}: Args) => {
|
||||
recurseRichText({
|
||||
children: (siblingDoc[field?.name] as SerializedEditorState)?.root?.children ?? [],
|
||||
context,
|
||||
@@ -112,15 +104,13 @@ export const richTextRelationshipPromise = async ({
|
||||
depth,
|
||||
editorPopulationPromises,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
promises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
@@ -14,12 +14,15 @@ type NestedRichTextFieldsArgs = {
|
||||
* This maps all the population promises to the node types
|
||||
*/
|
||||
editorPopulationPromises: Map<string, Array<PopulationPromise>>
|
||||
/**
|
||||
* fieldPromises are used for things like field hooks. They should be awaited before awaiting populationPromises
|
||||
*/
|
||||
fieldPromises: Promise<void>[]
|
||||
fields: Field[]
|
||||
findMany: boolean
|
||||
flattenLocales: boolean
|
||||
overrideAccess: boolean
|
||||
populationPromises: Promise<void>[]
|
||||
promises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
@@ -30,12 +33,12 @@ export const recurseNestedFields = ({
|
||||
currentDepth = 0,
|
||||
data,
|
||||
depth,
|
||||
fieldPromises,
|
||||
fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess = false,
|
||||
populationPromises,
|
||||
promises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
@@ -47,7 +50,7 @@ export const recurseNestedFields = ({
|
||||
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
|
||||
fallbackLocale: req.fallbackLocale,
|
||||
fieldPromises: promises, // Not sure if what I pass in here makes sense. But it doesn't seem like it's used at all anyways
|
||||
fieldPromises,
|
||||
fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
@@ -58,7 +61,7 @@ export const recurseNestedFields = ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
triggerAccessControl: false, // TODO: Enable this to support access control
|
||||
triggerHooks: false, // TODO: Enable this to support hooks
|
||||
//triggerAccessControl: false, // TODO: Enable this to support access control
|
||||
//triggerHooks: false, // TODO: Enable this to support hooks
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ type NestedRichTextFieldsArgs = {
|
||||
depth: number
|
||||
fields: Field[]
|
||||
overrideAccess: boolean
|
||||
promises: Promise<void>[]
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export const recurseNestedFields = ({
|
||||
depth,
|
||||
fields,
|
||||
overrideAccess = false,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}: NestedRichTextFieldsArgs): void => {
|
||||
@@ -34,7 +34,7 @@ export const recurseNestedFields = ({
|
||||
data[field.name].forEach(({ relationTo, value }, i) => {
|
||||
const collection = req.payload.collections[relationTo]
|
||||
if (collection) {
|
||||
promises.push(
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id: value,
|
||||
collection,
|
||||
@@ -54,7 +54,7 @@ export const recurseNestedFields = ({
|
||||
data[field.name].forEach((id, i) => {
|
||||
const collection = req.payload.collections[field.relationTo as string]
|
||||
if (collection) {
|
||||
promises.push(
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id,
|
||||
collection,
|
||||
@@ -78,7 +78,7 @@ export const recurseNestedFields = ({
|
||||
) {
|
||||
if (!('hasMany' in field) || !field.hasMany) {
|
||||
const collection = req.payload.collections[data[field.name].relationTo]
|
||||
promises.push(
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id: data[field.name].value,
|
||||
collection,
|
||||
@@ -97,7 +97,7 @@ export const recurseNestedFields = ({
|
||||
}
|
||||
if (typeof data[field.name] !== 'undefined' && typeof field.relationTo === 'string') {
|
||||
const collection = req.payload.collections[field.relationTo]
|
||||
promises.push(
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id: data[field.name],
|
||||
collection,
|
||||
@@ -120,7 +120,7 @@ export const recurseNestedFields = ({
|
||||
depth,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
@@ -131,7 +131,7 @@ export const recurseNestedFields = ({
|
||||
depth,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
@@ -144,7 +144,7 @@ export const recurseNestedFields = ({
|
||||
depth,
|
||||
fields: tab.fields,
|
||||
overrideAccess,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
@@ -160,7 +160,7 @@ export const recurseNestedFields = ({
|
||||
depth,
|
||||
fields: block.fields,
|
||||
overrideAccess,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
@@ -176,7 +176,7 @@ export const recurseNestedFields = ({
|
||||
depth,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
@@ -193,7 +193,7 @@ export const recurseNestedFields = ({
|
||||
depth,
|
||||
field,
|
||||
overrideAccess,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { AdapterArguments } from '../types.js'
|
||||
import { populate } from './populate.js'
|
||||
import { recurseNestedFields } from './recurseNestedFields.js'
|
||||
|
||||
export type Args = Parameters<RichTextAdapter<any[], AdapterArguments>['populationPromise']>[0]
|
||||
export type Args = Parameters<RichTextAdapter<any[], AdapterArguments>['populationPromises']>[0]
|
||||
|
||||
type RecurseRichTextArgs = {
|
||||
children: unknown[]
|
||||
@@ -13,7 +13,7 @@ type RecurseRichTextArgs = {
|
||||
depth: number
|
||||
field: RichTextField<any[], AdapterArguments, AdapterArguments>
|
||||
overrideAccess: boolean
|
||||
promises: Promise<void>[]
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export const recurseRichText = ({
|
||||
depth,
|
||||
field,
|
||||
overrideAccess = false,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}: RecurseRichTextArgs): void => {
|
||||
@@ -38,7 +38,7 @@ export const recurseRichText = ({
|
||||
const collection = req.payload.collections[element?.relationTo]
|
||||
|
||||
if (collection) {
|
||||
promises.push(
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id: element.value.id,
|
||||
collection,
|
||||
@@ -63,7 +63,7 @@ export const recurseRichText = ({
|
||||
depth,
|
||||
fields: field.admin.upload.collections[element.relationTo].fields,
|
||||
overrideAccess,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
@@ -75,7 +75,7 @@ export const recurseRichText = ({
|
||||
const collection = req.payload.collections[element?.doc?.relationTo]
|
||||
|
||||
if (collection) {
|
||||
promises.push(
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id: element.doc.value,
|
||||
collection,
|
||||
@@ -99,7 +99,7 @@ export const recurseRichText = ({
|
||||
depth,
|
||||
fields: field.admin?.link?.fields,
|
||||
overrideAccess,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
@@ -113,7 +113,7 @@ export const recurseRichText = ({
|
||||
depth,
|
||||
field,
|
||||
overrideAccess,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
@@ -122,27 +122,24 @@ export const recurseRichText = ({
|
||||
}
|
||||
}
|
||||
|
||||
export const richTextRelationshipPromise = async ({
|
||||
export const richTextRelationshipPromise = ({
|
||||
currentDepth,
|
||||
depth,
|
||||
field,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
}: Args): Promise<void> => {
|
||||
const promises = []
|
||||
|
||||
}: Args) => {
|
||||
recurseRichText({
|
||||
children: siblingDoc[field.name] as unknown[],
|
||||
currentDepth,
|
||||
depth,
|
||||
field,
|
||||
overrideAccess,
|
||||
promises,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
@@ -25,11 +25,12 @@ export function slateEditor(args: AdapterArguments): RichTextAdapter<any[], Adap
|
||||
},
|
||||
}
|
||||
},
|
||||
populationPromise({
|
||||
populationPromises({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
@@ -44,11 +45,12 @@ export function slateEditor(args: AdapterArguments): RichTextAdapter<any[], Adap
|
||||
field.admin?.elements?.includes('link') ||
|
||||
!field?.admin?.elements
|
||||
) {
|
||||
return richTextRelationshipPromise({
|
||||
richTextRelationshipPromise({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
field,
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
@@ -58,7 +60,6 @@ export function slateEditor(args: AdapterArguments): RichTextAdapter<any[], Adap
|
||||
siblingDoc,
|
||||
})
|
||||
}
|
||||
return null
|
||||
},
|
||||
validate: richTextValidate,
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type Args<T> = {
|
||||
siblingData: Data
|
||||
}
|
||||
|
||||
// TODO: Make this works for rich text subfields
|
||||
export const defaultValuePromise = async <T>({
|
||||
id,
|
||||
data,
|
||||
|
||||
@@ -33,9 +33,15 @@ let serverURL: string
|
||||
/**
|
||||
* Client-side navigation to the lexical editor from list view
|
||||
*/
|
||||
async function navigateToLexicalFields(navigateToListView: boolean = true) {
|
||||
async function navigateToLexicalFields(
|
||||
navigateToListView: boolean = true,
|
||||
localized: boolean = false,
|
||||
) {
|
||||
if (navigateToListView) {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'lexical-fields')
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(
|
||||
serverURL,
|
||||
localized ? 'lexical-localized-fields' : 'lexical-fields',
|
||||
)
|
||||
await page.goto(url.list)
|
||||
}
|
||||
|
||||
@@ -1101,7 +1107,7 @@ describe('lexical', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('should respect required error state in deeply nested text field', async () => {
|
||||
test('should respect required error state in deeply nested text field', async () => {
|
||||
await navigateToLexicalFields()
|
||||
const richTextField = page.locator('.rich-text-lexical').nth(1) // second
|
||||
await richTextField.scrollIntoViewIfNeeded()
|
||||
@@ -1117,12 +1123,18 @@ describe('lexical', () => {
|
||||
await page.click('#action-save', { delay: 100 })
|
||||
await expect(page.locator('.Toastify')).toContainText('The following field is invalid')
|
||||
|
||||
const requiredTooltip = conditionalArrayBlock
|
||||
.locator('.tooltip-content:has-text("This field is required.")')
|
||||
.first()
|
||||
await requiredTooltip.scrollIntoViewIfNeeded()
|
||||
// Check if error is shown next to field
|
||||
await expect(
|
||||
conditionalArrayBlock
|
||||
.locator('.tooltip-content:has-text("This field is required.")')
|
||||
.first(),
|
||||
).toBeVisible()
|
||||
await expect(requiredTooltip).toBeInViewport() // toBeVisible() doesn't work for some reason
|
||||
})
|
||||
})
|
||||
|
||||
describe('localization', () => {
|
||||
test.skip('ensure simple localized lexical field works', async () => {
|
||||
await navigateToLexicalFields(true, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'upload',
|
||||
version: 1,
|
||||
version: 2,
|
||||
fields: {
|
||||
caption: {
|
||||
root: {
|
||||
@@ -57,11 +57,9 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 1,
|
||||
version: 2,
|
||||
relationTo: 'text-fields',
|
||||
value: {
|
||||
id: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
@@ -69,9 +67,7 @@ export function generateLexicalRichText() {
|
||||
},
|
||||
},
|
||||
relationTo: 'uploads',
|
||||
value: {
|
||||
id: '{{UPLOAD_DOC_ID}}',
|
||||
},
|
||||
value: '{{UPLOAD_DOC_ID}}',
|
||||
},
|
||||
{
|
||||
format: '',
|
||||
@@ -120,11 +116,9 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 1,
|
||||
version: 2,
|
||||
relationTo: 'rich-text-fields',
|
||||
value: {
|
||||
id: '{{RICH_TEXT_DOC_ID}}',
|
||||
},
|
||||
value: '{{RICH_TEXT_DOC_ID}}',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
@@ -173,11 +167,9 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 1,
|
||||
version: 2,
|
||||
relationTo: 'text-fields',
|
||||
value: {
|
||||
id: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
|
||||
95
test/fields/collections/LexicalLocalized/index.ts
Normal file
95
test/fields/collections/LexicalLocalized/index.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
import { BlocksFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { lexicalLocalizedFieldsSlug } from '../../slugs.js'
|
||||
|
||||
export const LexicalLocalizedFields: CollectionConfig = {
|
||||
slug: lexicalLocalizedFieldsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
listSearchableFields: ['title'],
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'lexicalSimple',
|
||||
type: 'richText',
|
||||
localized: true,
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [...defaultFeatures],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'lexicalBlocksLocalized',
|
||||
admin: {
|
||||
description: 'Localized field with localized block subfields',
|
||||
},
|
||||
type: 'richText',
|
||||
localized: true,
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
{
|
||||
slug: 'block',
|
||||
fields: [
|
||||
{
|
||||
name: 'textLocalized',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'rel',
|
||||
type: 'relationship',
|
||||
relationTo: lexicalLocalizedFieldsSlug,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'lexicalBlocksSubLocalized',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
description: 'Non-localized field with localized block subfields',
|
||||
},
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
{
|
||||
slug: 'block',
|
||||
fields: [
|
||||
{
|
||||
name: 'textLocalized',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'rel',
|
||||
type: 'relationship',
|
||||
relationTo: lexicalLocalizedFieldsSlug,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { SerializedRelationshipNode } from '@payloadcms/richtext-lexical'
|
||||
import type { SerializedEditorState, SerializedParagraphNode, SerializedTextNode } from 'lexical'
|
||||
|
||||
import { lexicalLocalizedFieldsSlug } from '../../slugs.js'
|
||||
|
||||
export function textToLexicalJSON({
|
||||
text,
|
||||
lexicalLocalizedRelID,
|
||||
}: {
|
||||
lexicalLocalizedRelID?: number | string
|
||||
text: string
|
||||
}) {
|
||||
const editorJSON: SerializedEditorState = {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
direction: 'ltr',
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text,
|
||||
type: 'text',
|
||||
version: 1,
|
||||
} as SerializedTextNode,
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
} as SerializedParagraphNode,
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
if (lexicalLocalizedRelID) {
|
||||
editorJSON.root.children.push({
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 2,
|
||||
relationTo: lexicalLocalizedFieldsSlug,
|
||||
value: lexicalLocalizedRelID,
|
||||
} as SerializedRelationshipNode)
|
||||
}
|
||||
|
||||
return editorJSON
|
||||
}
|
||||
@@ -116,10 +116,8 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'relationship',
|
||||
version: 1,
|
||||
value: {
|
||||
id: '{{TEXT_DOC_ID}}',
|
||||
},
|
||||
version: 2,
|
||||
value: '{{TEXT_DOC_ID}}',
|
||||
relationTo: 'text-fields',
|
||||
},
|
||||
{
|
||||
@@ -230,11 +228,9 @@ export function generateLexicalRichText() {
|
||||
{
|
||||
format: '',
|
||||
type: 'upload',
|
||||
version: 1,
|
||||
version: 2,
|
||||
relationTo: 'uploads',
|
||||
value: {
|
||||
id: '{{UPLOAD_DOC_ID}}',
|
||||
},
|
||||
value: '{{UPLOAD_DOC_ID}}',
|
||||
fields: {
|
||||
caption: {
|
||||
root: {
|
||||
|
||||
@@ -12,6 +12,7 @@ import GroupFields from './collections/Group/index.js'
|
||||
import IndexedFields from './collections/Indexed/index.js'
|
||||
import JSONFields from './collections/JSON/index.js'
|
||||
import { LexicalFields } from './collections/Lexical/index.js'
|
||||
import { LexicalLocalizedFields } from './collections/LexicalLocalized/index.js'
|
||||
import { LexicalMigrateFields } from './collections/LexicalMigrate/index.js'
|
||||
import NumberFields from './collections/Number/index.js'
|
||||
import PointFields from './collections/Point/index.js'
|
||||
@@ -31,6 +32,7 @@ import { clearAndSeedEverything } from './seed.js'
|
||||
export const collectionSlugs: CollectionConfig[] = [
|
||||
LexicalFields,
|
||||
LexicalMigrateFields,
|
||||
LexicalLocalizedFields,
|
||||
{
|
||||
slug: 'users',
|
||||
admin: {
|
||||
|
||||
@@ -416,9 +416,10 @@ describe('Lexical', () => {
|
||||
/**
|
||||
* Depth 1 population:
|
||||
*/
|
||||
expect(subEditorRelationshipNode.value.id).toStrictEqual(createdRichTextDocID)
|
||||
expect(subEditorRelationshipNode.value).toStrictEqual(createdRichTextDocID)
|
||||
// But the value should not be populated and only have the id field:
|
||||
expect(Object.keys(subEditorRelationshipNode.value)).toHaveLength(1)
|
||||
|
||||
expect(typeof subEditorRelationshipNode.value).not.toStrictEqual('object')
|
||||
})
|
||||
|
||||
it('should populate relationship nodes inside of a sub-editor from a blocks node with 1 depth', async () => {
|
||||
@@ -463,9 +464,9 @@ describe('Lexical', () => {
|
||||
/**
|
||||
* Depth 2 population:
|
||||
*/
|
||||
expect(populatedDocEditorRelationshipNode.value.id).toStrictEqual(createdTextDocID)
|
||||
expect(populatedDocEditorRelationshipNode.value).toStrictEqual(createdTextDocID)
|
||||
// But the value should not be populated and only have the id field - that's because it would require a depth of 2
|
||||
expect(Object.keys(populatedDocEditorRelationshipNode.value)).toHaveLength(1)
|
||||
expect(populatedDocEditorRelationshipNode.value).not.toStrictEqual('object')
|
||||
})
|
||||
|
||||
it('should populate relationship nodes inside of a sub-editor from a blocks node with depth 2', async () => {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { dateDoc } from './collections/Date/shared.js'
|
||||
import { groupDoc } from './collections/Group/shared.js'
|
||||
import { jsonDoc } from './collections/JSON/shared.js'
|
||||
import { lexicalDocData } from './collections/Lexical/data.js'
|
||||
import { textToLexicalJSON } from './collections/LexicalLocalized/textToLexicalJSON.js'
|
||||
import { lexicalMigrateDocData } from './collections/LexicalMigrate/data.js'
|
||||
import { numberDoc } from './collections/Number/shared.js'
|
||||
import { pointDoc } from './collections/Point/shared.js'
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
groupFieldsSlug,
|
||||
jsonFieldsSlug,
|
||||
lexicalFieldsSlug,
|
||||
lexicalLocalizedFieldsSlug,
|
||||
lexicalMigrateFieldsSlug,
|
||||
numberFieldsSlug,
|
||||
pointFieldsSlug,
|
||||
@@ -49,7 +51,7 @@ import {
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export const seed = async (_payload) => {
|
||||
export const seed = async (_payload: Payload) => {
|
||||
if (_payload.db.name === 'mongoose') {
|
||||
await Promise.all(
|
||||
_payload.config.collections.map(async (coll) => {
|
||||
@@ -274,6 +276,74 @@ export const seed = async (_payload) => {
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
const lexicalLocalizedDoc1 = await _payload.create({
|
||||
collection: lexicalLocalizedFieldsSlug,
|
||||
data: {
|
||||
title: 'Localized Lexical en',
|
||||
lexicalSimple: textToLexicalJSON({ text: 'English text' }),
|
||||
lexicalBlocksLocalized: textToLexicalJSON({ text: 'English text' }),
|
||||
lexicalBlocksSubLocalized: textToLexicalJSON({ text: 'English text' }),
|
||||
},
|
||||
locale: 'en',
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
await _payload.update({
|
||||
collection: lexicalLocalizedFieldsSlug,
|
||||
id: lexicalLocalizedDoc1.id,
|
||||
data: {
|
||||
title: 'Localized Lexical es',
|
||||
lexicalSimple: textToLexicalJSON({ text: 'Spanish text' }),
|
||||
lexicalBlocksLocalized: textToLexicalJSON({ text: 'Spanish text' }),
|
||||
lexicalBlocksSubLocalized: textToLexicalJSON({ text: 'Spanish text' }),
|
||||
},
|
||||
locale: 'es',
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
const lexicalLocalizedDoc2 = await _payload.create({
|
||||
collection: lexicalLocalizedFieldsSlug,
|
||||
data: {
|
||||
title: 'Localized Lexical en 2',
|
||||
lexicalSimple: textToLexicalJSON({
|
||||
text: 'English text 2',
|
||||
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
||||
}),
|
||||
lexicalBlocksLocalized: textToLexicalJSON({
|
||||
text: 'English text 2',
|
||||
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
||||
}),
|
||||
lexicalBlocksSubLocalized: textToLexicalJSON({
|
||||
text: 'English text 2',
|
||||
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
||||
}),
|
||||
},
|
||||
locale: 'en',
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
await _payload.update({
|
||||
collection: lexicalLocalizedFieldsSlug,
|
||||
id: lexicalLocalizedDoc2.id,
|
||||
data: {
|
||||
title: 'Localized Lexical es 2',
|
||||
lexicalSimple: textToLexicalJSON({
|
||||
text: 'Spanish text 2',
|
||||
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
||||
}),
|
||||
lexicalBlocksLocalized: textToLexicalJSON({
|
||||
text: 'Spanish text 2',
|
||||
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
||||
}),
|
||||
},
|
||||
locale: 'es',
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
await _payload.create({
|
||||
collection: lexicalMigrateFieldsSlug,
|
||||
data: lexicalMigrateDocWithRelId,
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
export const usersSlug = 'users' as const
|
||||
export const arrayFieldsSlug = 'array-fields' as const
|
||||
export const blockFieldsSlug = 'block-fields' as const
|
||||
export const checkboxFieldsSlug = 'checkbox-fields' as const
|
||||
export const codeFieldsSlug = 'code-fields' as const
|
||||
export const collapsibleFieldsSlug = 'collapsible-fields' as const
|
||||
export const conditionalLogicSlug = 'conditional-logic' as const
|
||||
export const dateFieldsSlug = 'date-fields' as const
|
||||
export const groupFieldsSlug = 'group-fields' as const
|
||||
export const indexedFieldsSlug = 'indexed-fields' as const
|
||||
export const jsonFieldsSlug = 'json-fields' as const
|
||||
export const lexicalFieldsSlug = 'lexical-fields' as const
|
||||
export const lexicalMigrateFieldsSlug = 'lexical-migrate-fields' as const
|
||||
export const numberFieldsSlug = 'number-fields' as const
|
||||
export const pointFieldsSlug = 'point-fields' as const
|
||||
export const radioFieldsSlug = 'radio-fields' as const
|
||||
export const relationshipFieldsSlug = 'relationship-fields' as const
|
||||
export const richTextFieldsSlug = 'rich-text-fields' as const
|
||||
export const rowFieldsSlug = 'row-fields' as const
|
||||
export const selectFieldsSlug = 'select-fields' as const
|
||||
export const tabsFieldsSlug = 'tabs-fields' as const
|
||||
export const textFieldsSlug = 'text-fields' as const
|
||||
export const uploadsSlug = 'uploads' as const
|
||||
export const uploads2Slug = 'uploads2' as const
|
||||
export const uploads3Slug = 'uploads3' as const
|
||||
export const usersSlug = 'users'
|
||||
export const arrayFieldsSlug = 'array-fields'
|
||||
export const blockFieldsSlug = 'block-fields'
|
||||
export const checkboxFieldsSlug = 'checkbox-fields'
|
||||
export const codeFieldsSlug = 'code-fields'
|
||||
export const collapsibleFieldsSlug = 'collapsible-fields'
|
||||
export const conditionalLogicSlug = 'conditional-logic'
|
||||
export const dateFieldsSlug = 'date-fields'
|
||||
export const groupFieldsSlug = 'group-fields'
|
||||
export const indexedFieldsSlug = 'indexed-fields'
|
||||
export const jsonFieldsSlug = 'json-fields'
|
||||
export const lexicalFieldsSlug = 'lexical-fields'
|
||||
export const lexicalLocalizedFieldsSlug = 'lexical-localized-fields'
|
||||
export const lexicalMigrateFieldsSlug = 'lexical-migrate-fields'
|
||||
export const numberFieldsSlug = 'number-fields'
|
||||
export const pointFieldsSlug = 'point-fields'
|
||||
export const radioFieldsSlug = 'radio-fields'
|
||||
export const relationshipFieldsSlug = 'relationship-fields'
|
||||
export const richTextFieldsSlug = 'rich-text-fields'
|
||||
export const rowFieldsSlug = 'row-fields'
|
||||
export const selectFieldsSlug = 'select-fields'
|
||||
export const tabsFieldsSlug = 'tabs-fields'
|
||||
export const textFieldsSlug = 'text-fields'
|
||||
export const uploadsSlug = 'uploads'
|
||||
export const uploads2Slug = 'uploads2'
|
||||
export const uploads3Slug = 'uploads3'
|
||||
export const collectionSlugs = [
|
||||
usersSlug,
|
||||
arrayFieldsSlug,
|
||||
|
||||
Reference in New Issue
Block a user