Merge branch 'alpha' of github.com:payloadcms/payload into chore/pre-build-e2e
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-alpha.50",
|
||||
"version": "3.0.0-alpha.54",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"homepage": "https://payloadcms.com",
|
||||
|
||||
@@ -106,43 +106,42 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
required
|
||||
/>
|
||||
<ConfirmPassword disabled={readOnly} />
|
||||
{!requirePassword && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
onClick={() => handleChangePassword(false)}
|
||||
size="small"
|
||||
>
|
||||
{t('general:cancel')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{((!changingPassword && !requirePassword) || operation === 'update') && (
|
||||
<div className={`${baseClass}__controls`}>
|
||||
{!changingPassword && !requirePassword && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
id="change-password"
|
||||
onClick={() => handleChangePassword(true)}
|
||||
size="small"
|
||||
>
|
||||
{t('authentication:changePassword')}
|
||||
</Button>
|
||||
)}
|
||||
{operation === 'update' && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
onClick={() => unlock()}
|
||||
size="small"
|
||||
>
|
||||
{t('authentication:forceUnlock')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`${baseClass}__controls`}>
|
||||
{changingPassword && !requirePassword && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
onClick={() => handleChangePassword(false)}
|
||||
size="small"
|
||||
>
|
||||
{t('general:cancel')}
|
||||
</Button>
|
||||
)}
|
||||
{!changingPassword && !requirePassword && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
id="change-password"
|
||||
onClick={() => handleChangePassword(true)}
|
||||
size="small"
|
||||
>
|
||||
{t('authentication:changePassword')}
|
||||
</Button>
|
||||
)}
|
||||
{operation === 'update' && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={readOnly}
|
||||
onClick={() => unlock()}
|
||||
size="small"
|
||||
>
|
||||
{t('authentication:forceUnlock')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{useAPIKey && (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { LivePreviewConfig } from 'payload/config'
|
||||
import type { EditViewComponent } from 'payload/types'
|
||||
import type { EditViewComponent, TypeWithID } from 'payload/types'
|
||||
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import React from 'react'
|
||||
|
||||
import { LivePreviewClient } from './index.client.js'
|
||||
@@ -11,6 +12,7 @@ export const LivePreviewView: EditViewComponent = async (props) => {
|
||||
|
||||
const {
|
||||
collectionConfig,
|
||||
docID,
|
||||
globalConfig,
|
||||
locale,
|
||||
req: {
|
||||
@@ -22,8 +24,30 @@ export const LivePreviewView: EditViewComponent = async (props) => {
|
||||
} = {},
|
||||
} = initPageResult
|
||||
|
||||
// TODO(JAKE): not sure what `data` is or what it should be
|
||||
const data = 'data' in props ? props.data : {}
|
||||
let data: TypeWithID
|
||||
|
||||
try {
|
||||
if (collectionConfig) {
|
||||
data = await initPageResult.req.payload.findByID({
|
||||
id: docID,
|
||||
collection: collectionConfig.slug,
|
||||
depth: 0,
|
||||
draft: true,
|
||||
fallbackLocale: null,
|
||||
})
|
||||
}
|
||||
|
||||
if (globalConfig) {
|
||||
data = await initPageResult.req.payload.findGlobal({
|
||||
slug: globalConfig.slug,
|
||||
depth: 0,
|
||||
draft: true,
|
||||
fallbackLocale: null,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
let livePreviewConfig: LivePreviewConfig = topLevelLivePreviewConfig
|
||||
|
||||
@@ -54,10 +78,11 @@ export const LivePreviewView: EditViewComponent = async (props) => {
|
||||
const url =
|
||||
typeof livePreviewConfig?.url === 'function'
|
||||
? await livePreviewConfig.url({
|
||||
collectionConfig,
|
||||
data,
|
||||
documentInfo: {}, // TODO: recreate this object server-side, see `useDocumentInfo`
|
||||
// @ts-expect-error
|
||||
globalConfig,
|
||||
locale,
|
||||
payload: initPageResult.req.payload,
|
||||
})
|
||||
: livePreviewConfig?.url
|
||||
|
||||
|
||||
@@ -55,14 +55,16 @@ const Select: React.FC<
|
||||
|
||||
if (version === comparison) placeholder = `[${i18n.t('general:noValue')}]`
|
||||
|
||||
const options = 'options' in field.fieldComponentProps && field.fieldComponentProps.options
|
||||
|
||||
const comparisonToRender =
|
||||
typeof comparison !== 'undefined'
|
||||
? getTranslatedOptions(getOptionsToRender(comparison, field.options, field.hasMany), i18n)
|
||||
? getTranslatedOptions(getOptionsToRender(comparison, options, field.hasMany), i18n)
|
||||
: placeholder
|
||||
|
||||
const versionToRender =
|
||||
typeof version !== 'undefined'
|
||||
? getTranslatedOptions(getOptionsToRender(version, field.options, field.hasMany), i18n)
|
||||
? getTranslatedOptions(getOptionsToRender(version, options, field.hasMany), i18n)
|
||||
: placeholder
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Translations } from '@payloadcms/translations'
|
||||
|
||||
import type { Permissions } from '../../auth/index.js'
|
||||
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
|
||||
import type { Locale } from '../../config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
|
||||
import type { PayloadRequest } from '../../types/index.js'
|
||||
|
||||
|
||||
@@ -63,9 +63,11 @@ export type LivePreviewConfig = {
|
||||
*/
|
||||
url?:
|
||||
| ((args: {
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
data: Record<string, any>
|
||||
documentInfo: any // TODO: remove or populate this
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
locale: Locale
|
||||
payload: Payload
|
||||
}) => Promise<string> | string)
|
||||
| string
|
||||
}
|
||||
|
||||
14
packages/payload/src/errors/MissingEditorProp.ts
Normal file
14
packages/payload/src/errors/MissingEditorProp.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { Field } from '../fields/config/types.js'
|
||||
|
||||
import { fieldAffectsData } from '../fields/config/types.js'
|
||||
import APIError from './APIError.js'
|
||||
|
||||
class MissingEditorProp extends APIError {
|
||||
constructor(field: Field) {
|
||||
super(
|
||||
`RichText field${fieldAffectsData(field) ? ` "${field.name}"` : ''} is missing the editor prop`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MissingEditorProp
|
||||
@@ -70,6 +70,7 @@ export type {
|
||||
FieldWithMany,
|
||||
FieldWithMaxDepth,
|
||||
FieldWithPath,
|
||||
FieldWithRichTextRequiredEditor,
|
||||
FieldWithSubFields,
|
||||
FilterOptions,
|
||||
FilterOptionsProps,
|
||||
@@ -88,6 +89,7 @@ export type {
|
||||
RelationshipField,
|
||||
RelationshipValue,
|
||||
RichTextField,
|
||||
RichTextFieldRequiredEditor,
|
||||
RowAdmin,
|
||||
RowField,
|
||||
SelectField,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Field } from '../config/types.js'
|
||||
import type { FieldWithRichTextRequiredEditor } from '../config/types.js'
|
||||
|
||||
import { baseIDField } from './baseIDField.js'
|
||||
|
||||
export const baseBlockFields: Field[] = [
|
||||
export const baseBlockFields: FieldWithRichTextRequiredEditor[] = [
|
||||
baseIDField,
|
||||
{
|
||||
name: 'blockName',
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import ObjectIdImport from 'bson-objectid'
|
||||
|
||||
import type { Field } from '../config/types.js'
|
||||
import type { TextField } from '../config/types.js'
|
||||
|
||||
const ObjectId = (ObjectIdImport.default ||
|
||||
ObjectIdImport) as unknown as typeof ObjectIdImport.default
|
||||
|
||||
export const baseIDField: Field = {
|
||||
export const baseIDField: TextField = {
|
||||
name: 'id',
|
||||
type: 'text',
|
||||
admin: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Config } from '../../config/types.js'
|
||||
import type { Field } from './types.js'
|
||||
|
||||
import MissingEditorProp from '../../errors/MissingEditorProp.js'
|
||||
import {
|
||||
DuplicateFieldName,
|
||||
InvalidFieldName,
|
||||
@@ -18,6 +19,12 @@ type Args = {
|
||||
config: Config
|
||||
existingFieldNames?: Set<string>
|
||||
fields: Field[]
|
||||
/**
|
||||
* If true, a richText field will require an editor property to be set, as the sanitizeFields function will not add it from the payload config if not present.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
requireFieldLevelRichTextEditor?: boolean
|
||||
/**
|
||||
* If not null, will validate that upload and relationship fields do not relate to a collection that is not in this array.
|
||||
* This validation will be skipped if validRelationships is null.
|
||||
@@ -29,6 +36,7 @@ export const sanitizeFields = ({
|
||||
config,
|
||||
existingFieldNames = new Set(),
|
||||
fields,
|
||||
requireFieldLevelRichTextEditor = false,
|
||||
validRelationships,
|
||||
}: Args): Field[] => {
|
||||
if (!fields) return []
|
||||
@@ -44,8 +52,12 @@ export const sanitizeFields = ({
|
||||
}
|
||||
|
||||
// Make sure that the richText field has an editor
|
||||
if (field.type === 'richText' && !field.editor && config.editor) {
|
||||
field.editor = config.editor
|
||||
if (field.type === 'richText' && !field.editor) {
|
||||
if (config.editor && !requireFieldLevelRichTextEditor) {
|
||||
field.editor = config.editor
|
||||
} else {
|
||||
throw new MissingEditorProp(field)
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-label
|
||||
@@ -143,6 +155,7 @@ export const sanitizeFields = ({
|
||||
config,
|
||||
existingFieldNames: fieldAffectsData(field) ? new Set() : existingFieldNames,
|
||||
fields: field.fields,
|
||||
requireFieldLevelRichTextEditor,
|
||||
validRelationships,
|
||||
})
|
||||
}
|
||||
@@ -158,6 +171,7 @@ export const sanitizeFields = ({
|
||||
config,
|
||||
existingFieldNames: tabHasName(tab) ? new Set() : existingFieldNames,
|
||||
fields: tab.fields,
|
||||
requireFieldLevelRichTextEditor,
|
||||
validRelationships,
|
||||
})
|
||||
|
||||
@@ -176,6 +190,7 @@ export const sanitizeFields = ({
|
||||
config,
|
||||
existingFieldNames: new Set(),
|
||||
fields: block.fields,
|
||||
requireFieldLevelRichTextEditor,
|
||||
validRelationships,
|
||||
})
|
||||
|
||||
|
||||
@@ -569,6 +569,14 @@ export type RichTextField<
|
||||
type: 'richText'
|
||||
} & ExtraProperties
|
||||
|
||||
export type RichTextFieldRequiredEditor<
|
||||
Value extends object = any,
|
||||
AdapterProps = any,
|
||||
ExtraProperties = object,
|
||||
> = Omit<RichTextField<Value, AdapterProps, ExtraProperties>, 'editor'> & {
|
||||
editor: RichTextAdapter<Value, AdapterProps, ExtraProperties>
|
||||
}
|
||||
|
||||
export type ArrayField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
components?: {
|
||||
@@ -662,6 +670,10 @@ export type Field =
|
||||
| UIField
|
||||
| UploadField
|
||||
|
||||
export type FieldWithRichTextRequiredEditor =
|
||||
| Exclude<Field, RichTextField>
|
||||
| RichTextFieldRequiredEditor
|
||||
|
||||
export type FieldAffectingData =
|
||||
| ArrayField
|
||||
| BlockField
|
||||
|
||||
@@ -26,36 +26,6 @@ const generateURL = ({ collectionSlug, config, filename }: GenerateURLArgs) => {
|
||||
return undefined
|
||||
}
|
||||
|
||||
type Args = {
|
||||
collectionConfig?: CollectionConfig
|
||||
config: Config
|
||||
doc: Record<string, unknown>
|
||||
}
|
||||
const generateAdminThumbnails = ({ collectionConfig, config, doc }: Args) => {
|
||||
const adminThumbnail =
|
||||
typeof collectionConfig.upload !== 'boolean'
|
||||
? collectionConfig.upload?.adminThumbnail
|
||||
: undefined
|
||||
|
||||
if (typeof adminThumbnail === 'function') {
|
||||
return adminThumbnail({ doc })
|
||||
}
|
||||
|
||||
if ('sizes' in doc && doc.sizes?.[adminThumbnail]?.filename) {
|
||||
return generateURL({
|
||||
collectionSlug: collectionConfig.slug,
|
||||
config,
|
||||
filename: doc.sizes?.[adminThumbnail].filename as string,
|
||||
})
|
||||
}
|
||||
|
||||
return generateURL({
|
||||
collectionSlug: collectionConfig.slug,
|
||||
config,
|
||||
filename: doc.filename as string,
|
||||
})
|
||||
}
|
||||
|
||||
type Options = {
|
||||
collection: CollectionConfig
|
||||
config: Config
|
||||
@@ -74,16 +44,6 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
|
||||
label: 'MIME Type',
|
||||
}
|
||||
|
||||
const url: Field = {
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
admin: {
|
||||
hidden: true,
|
||||
readOnly: true,
|
||||
},
|
||||
label: 'URL',
|
||||
}
|
||||
|
||||
const thumbnailURL: Field = {
|
||||
name: 'thumbnailURL',
|
||||
type: 'text',
|
||||
@@ -93,12 +53,28 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
|
||||
},
|
||||
hooks: {
|
||||
afterRead: [
|
||||
({ data }) =>
|
||||
generateAdminThumbnails({
|
||||
collectionConfig: collection,
|
||||
config,
|
||||
doc: data,
|
||||
}),
|
||||
({ originalDoc }) => {
|
||||
const adminThumbnail =
|
||||
typeof collection.upload !== 'boolean' ? collection.upload?.adminThumbnail : undefined
|
||||
|
||||
if (typeof adminThumbnail === 'function') {
|
||||
return adminThumbnail({ doc: originalDoc })
|
||||
}
|
||||
|
||||
if (
|
||||
typeof adminThumbnail === 'string' &&
|
||||
'sizes' in originalDoc &&
|
||||
originalDoc.sizes?.[adminThumbnail]?.filename
|
||||
) {
|
||||
return generateURL({
|
||||
collectionSlug: collection.slug,
|
||||
config,
|
||||
filename: originalDoc.sizes?.[adminThumbnail].filename as string,
|
||||
})
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
],
|
||||
},
|
||||
label: 'Thumbnail URL',
|
||||
@@ -147,6 +123,16 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
|
||||
unique: true,
|
||||
}
|
||||
|
||||
const url: Field = {
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
admin: {
|
||||
hidden: true,
|
||||
readOnly: true,
|
||||
},
|
||||
label: 'URL',
|
||||
}
|
||||
|
||||
let uploadFields: Field[] = [
|
||||
{
|
||||
...url,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Block, BlockField, Field } from 'payload/types'
|
||||
import type { Block, BlockField, Field, FieldWithRichTextRequiredEditor } from 'payload/types'
|
||||
|
||||
import { baseBlockFields, sanitizeFields } from 'payload/config'
|
||||
import { fieldsToJSONSchema, formatLabels } from 'payload/utilities'
|
||||
@@ -13,7 +13,11 @@ import { blockPopulationPromiseHOC } from './populationPromise.js'
|
||||
import { blockValidationHOC } from './validate.js'
|
||||
|
||||
export type BlocksFeatureProps = {
|
||||
blocks: Block[]
|
||||
blocks: Array<
|
||||
Omit<Block, 'fields'> & {
|
||||
fields: FieldWithRichTextRequiredEditor[]
|
||||
}
|
||||
>
|
||||
}
|
||||
|
||||
export const BlocksFeature: FeatureProviderProviderServer<
|
||||
@@ -118,6 +122,7 @@ export const BlocksFeature: FeatureProviderProviderServer<
|
||||
fields: sanitizeFields({
|
||||
config,
|
||||
fields: blockCopy.fields,
|
||||
requireFieldLevelRichTextEditor: true,
|
||||
validRelationships,
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export const blockPopulationPromiseHOC = (
|
||||
block.fields = sanitizeFields({
|
||||
config: payloadConfig,
|
||||
fields: block.fields,
|
||||
requireFieldLevelRichTextEditor: true,
|
||||
validRelationships,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,6 +28,7 @@ export const blockValidationHOC = (
|
||||
block.fields = sanitizeFields({
|
||||
config,
|
||||
fields: block.fields,
|
||||
requireFieldLevelRichTextEditor: true,
|
||||
validRelationships,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type { SanitizedConfig } from 'payload/config'
|
||||
import type { Field } from 'payload/types'
|
||||
import type { FieldWithRichTextRequiredEditor } from 'payload/types'
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { translations } from '@payloadcms/translations/client'
|
||||
@@ -45,8 +45,12 @@ export type LinkFeatureServerProps = ExclusiveLinkCollectionsProps & {
|
||||
* displayed in the link editor drawer.
|
||||
*/
|
||||
fields?:
|
||||
| ((args: { config: SanitizedConfig; defaultFields: Field[]; i18n: I18n }) => Field[])
|
||||
| Field[]
|
||||
| ((args: {
|
||||
config: SanitizedConfig
|
||||
defaultFields: FieldWithRichTextRequiredEditor[]
|
||||
i18n: I18n
|
||||
}) => FieldWithRichTextRequiredEditor[])
|
||||
| FieldWithRichTextRequiredEditor[]
|
||||
}
|
||||
|
||||
export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps, ClientProps> = (
|
||||
|
||||
@@ -75,7 +75,7 @@ const Component: React.FC<ElementProps> = (props) => {
|
||||
{ initialParams },
|
||||
)
|
||||
|
||||
const thumbnailSRC = data?.thumbnailURL
|
||||
const thumbnailSRC = data?.thumbnailURL || data?.url
|
||||
|
||||
const removeUpload = useCallback(() => {
|
||||
editor.update(() => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Field, Payload } from 'payload/types'
|
||||
import type { Field, FieldWithRichTextRequiredEditor, Payload } from 'payload/types'
|
||||
|
||||
import type { HTMLConverter } from '../converters/html/converter/types.js'
|
||||
import type { FeatureProviderProviderServer } from '../types.js'
|
||||
@@ -12,7 +12,7 @@ import { uploadValidation } from './validate.js'
|
||||
export type UploadFeatureProps = {
|
||||
collections: {
|
||||
[collection: string]: {
|
||||
fields: Field[]
|
||||
fields: FieldWithRichTextRequiredEditor[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ export const getGenerateComponentMap =
|
||||
const sanitizedFields = sanitizeFields({
|
||||
config,
|
||||
fields: cloneDeep(fields),
|
||||
requireFieldLevelRichTextEditor: true,
|
||||
validRelationships,
|
||||
})
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ export const getGenerateSchemaMap =
|
||||
const sanitizedFields = sanitizeFields({
|
||||
config,
|
||||
fields: cloneDeep(fields),
|
||||
requireFieldLevelRichTextEditor: true,
|
||||
validRelationships,
|
||||
})
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ const UploadElement: React.FC<Props & { enabledCollectionSlugs?: string[] }> = (
|
||||
{ initialParams },
|
||||
)
|
||||
|
||||
const thumbnailSRC = data?.thumbnailURL
|
||||
const thumbnailSRC = data?.thumbnailURL || data?.url
|
||||
|
||||
const removeUpload = useCallback(() => {
|
||||
const elementPath = ReactEditor.findPath(editor, element)
|
||||
|
||||
@@ -35,7 +35,7 @@ export const FileDetails: React.FC<FileDetailsProps> = (props) => {
|
||||
<Thumbnail
|
||||
collectionSlug={collectionSlug}
|
||||
doc={doc}
|
||||
fileSrc={thumbnailURL}
|
||||
fileSrc={thumbnailURL || url}
|
||||
imageCacheTag={imageCacheTag}
|
||||
uploadConfig={uploadConfig}
|
||||
/>
|
||||
|
||||
@@ -26,7 +26,7 @@ export const FileCell: React.FC<FileCellProps> = ({
|
||||
...rowData,
|
||||
filename,
|
||||
}}
|
||||
fileSrc={rowData?.thumbnailURL}
|
||||
fileSrc={rowData?.thumbnailURL || rowData?.url}
|
||||
size="small"
|
||||
uploadConfig={uploadConfig}
|
||||
/>
|
||||
|
||||
@@ -65,12 +65,13 @@ export const _ArrayField: React.FC<ArrayFieldProps> = (props) => {
|
||||
minRows,
|
||||
path: pathFromProps,
|
||||
permissions,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const { indexPath } = useFieldProps()
|
||||
const { indexPath, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { setDocFieldPreferences } = useDocumentInfo()
|
||||
const { addFieldRow, dispatchFields, setModified } = useForm()
|
||||
|
||||
@@ -69,12 +69,13 @@ const _BlocksField: React.FC<BlocksFieldProps> = (props) => {
|
||||
maxRows,
|
||||
minRows,
|
||||
path: pathFromProps,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const { indexPath } = useFieldProps()
|
||||
const { indexPath, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { setDocFieldPreferences } = useDocumentInfo()
|
||||
const { addFieldRow, dispatchFields, setModified } = useForm()
|
||||
|
||||
@@ -39,7 +39,7 @@ const CheckboxField: React.FC<CheckboxFieldProps> = (props) => {
|
||||
onChange: onChangeFromProps,
|
||||
partialChecked,
|
||||
path: pathFromProps,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
style,
|
||||
validate,
|
||||
@@ -57,7 +57,8 @@ const CheckboxField: React.FC<CheckboxFieldProps> = (props) => {
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { path, setValue, showError, value } = useField({
|
||||
disableFormData,
|
||||
|
||||
@@ -48,7 +48,7 @@ const CodeField: React.FC<CodeFieldProps> = (props) => {
|
||||
labelProps,
|
||||
language = 'javascript',
|
||||
path: pathFromProps,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
style,
|
||||
validate,
|
||||
@@ -64,7 +64,8 @@ const CodeField: React.FC<CodeFieldProps> = (props) => {
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { path, setValue, showError, value } = useField({
|
||||
path: pathFromContext || pathFromProps || name,
|
||||
|
||||
@@ -43,12 +43,13 @@ const CollapsibleField: React.FC<CollapsibleFieldProps> = (props) => {
|
||||
initCollapsed = false,
|
||||
labelProps,
|
||||
path: pathFromProps,
|
||||
readOnly: readOnlyFromProps,
|
||||
} = props
|
||||
|
||||
const {
|
||||
indexPath,
|
||||
path: pathFromContext,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromContext,
|
||||
schemaPath,
|
||||
siblingPermissions,
|
||||
} = useFieldProps()
|
||||
@@ -110,6 +111,8 @@ const CollapsibleField: React.FC<CollapsibleFieldProps> = (props) => {
|
||||
|
||||
if (typeof collapsedOnMount !== 'boolean') return null
|
||||
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<WatchChildErrors fieldMap={fieldMap} path={path} setErrorCount={setErrorCount} />
|
||||
|
||||
@@ -49,7 +49,7 @@ const DateTimeField: React.FC<DateFieldProps> = (props) => {
|
||||
labelProps,
|
||||
path: pathFromProps,
|
||||
placeholder,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
style,
|
||||
validate,
|
||||
@@ -67,13 +67,15 @@ const DateTimeField: React.FC<DateFieldProps> = (props) => {
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
|
||||
const { path, setValue, showError, value } = useField<Date>({
|
||||
path: pathFromContext || pathFromProps || name,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
|
||||
@@ -42,7 +42,7 @@ const EmailField: React.FC<EmailFieldProps> = (props) => {
|
||||
labelProps,
|
||||
path: pathFromProps,
|
||||
placeholder,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
style,
|
||||
validate,
|
||||
@@ -60,7 +60,8 @@ const EmailField: React.FC<EmailFieldProps> = (props) => {
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { path, setValue, showError, value } = useField({
|
||||
path: pathFromContext || pathFromProps || name,
|
||||
@@ -88,7 +89,7 @@ const EmailField: React.FC<EmailFieldProps> = (props) => {
|
||||
{BeforeInput}
|
||||
<input
|
||||
autoComplete={autoComplete}
|
||||
disabled={Boolean(readOnly)}
|
||||
disabled={readOnly}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={setValue}
|
||||
|
||||
@@ -44,11 +44,12 @@ const GroupField: React.FC<GroupFieldProps> = (props) => {
|
||||
fieldMap,
|
||||
hideGutter,
|
||||
labelProps,
|
||||
readOnly: readOnlyFromProps,
|
||||
style,
|
||||
width,
|
||||
} = props
|
||||
|
||||
const { path, permissions, readOnly, schemaPath } = useFieldProps()
|
||||
const { path, permissions, readOnly: readOnlyFromContext, schemaPath } = useFieldProps()
|
||||
const { i18n } = useTranslation()
|
||||
const isWithinCollapsible = useCollapsible()
|
||||
const isWithinGroup = useGroup()
|
||||
@@ -58,6 +59,7 @@ const GroupField: React.FC<GroupFieldProps> = (props) => {
|
||||
const submitted = useFormSubmitted()
|
||||
const errorCount = errorPaths.length
|
||||
const fieldHasErrors = submitted && errorCount > 0
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const isTopLevel = !(isWithinCollapsible || isWithinGroup || isWithinRow)
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ const JSONFieldComponent: React.FC<JSONFieldProps> = (props) => {
|
||||
label,
|
||||
labelProps,
|
||||
path: pathFromProps,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
style,
|
||||
validate,
|
||||
@@ -62,7 +62,8 @@ const JSONFieldComponent: React.FC<JSONFieldProps> = (props) => {
|
||||
[validate, required, jsonError],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { initialValue, path, setValue, showError, value } = useField<string>({
|
||||
path: pathFromContext || pathFromProps || name,
|
||||
|
||||
@@ -54,7 +54,7 @@ const NumberFieldComponent: React.FC<NumberFieldProps> = (props) => {
|
||||
onChange: onChangeFromProps,
|
||||
path: pathFromProps,
|
||||
placeholder,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
step = 1,
|
||||
style,
|
||||
@@ -73,7 +73,8 @@ const NumberFieldComponent: React.FC<NumberFieldProps> = (props) => {
|
||||
[validate, min, max, required],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { path, setValue, showError, value } = useField<number | number[]>({
|
||||
path: pathFromContext || pathFromProps || name,
|
||||
|
||||
@@ -43,7 +43,7 @@ const PointField: React.FC<PointFieldProps> = (props) => {
|
||||
labelProps,
|
||||
path: pathFromProps,
|
||||
placeholder,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
step,
|
||||
style,
|
||||
@@ -62,7 +62,8 @@ const PointField: React.FC<PointFieldProps> = (props) => {
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const {
|
||||
path,
|
||||
@@ -89,7 +90,8 @@ const PointField: React.FC<PointFieldProps> = (props) => {
|
||||
|
||||
const getCoordinateFieldLabel = (type: 'latitude' | 'longitude') => {
|
||||
const suffix = type === 'longitude' ? t('fields:longitude') : t('fields:latitude')
|
||||
const fieldLabel = labelProps && labelProps.label ? labelProps.label : ''
|
||||
const fieldLabel = labelProps && labelProps.label ? getTranslation(labelProps.label, i18n) : ''
|
||||
|
||||
return {
|
||||
...labelProps,
|
||||
label: `${fieldLabel}${fieldLabel ? ' - ' : ''}${suffix}`,
|
||||
|
||||
@@ -50,7 +50,7 @@ const RadioGroupField: React.FC<RadioFieldProps> = (props) => {
|
||||
onChange: onChangeFromProps,
|
||||
options = [],
|
||||
path: pathFromProps,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
style,
|
||||
validate,
|
||||
@@ -68,7 +68,8 @@ const RadioGroupField: React.FC<RadioFieldProps> = (props) => {
|
||||
[validate, options, required],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const {
|
||||
path,
|
||||
|
||||
@@ -52,7 +52,7 @@ const RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
|
||||
label,
|
||||
labelProps,
|
||||
path: pathFromProps,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
relationTo,
|
||||
required,
|
||||
sortOptions,
|
||||
@@ -92,7 +92,8 @@ const RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { filterOptions, initialValue, path, setValue, showError, value } = useField<
|
||||
Value | Value[]
|
||||
|
||||
@@ -62,7 +62,7 @@ const SelectField: React.FC<SelectFieldProps> = (props) => {
|
||||
onChange: onChangeFromProps,
|
||||
options: optionsFromProps = [],
|
||||
path: pathFromProps,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
style,
|
||||
validate,
|
||||
@@ -81,7 +81,8 @@ const SelectField: React.FC<SelectFieldProps> = (props) => {
|
||||
[validate, required, hasMany, options],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { path, setValue, showError, value } = useField({
|
||||
path: pathFromContext || pathFromProps || name,
|
||||
|
||||
@@ -43,11 +43,18 @@ const TabsField: React.FC<TabsFieldProps> = (props) => {
|
||||
descriptionProps,
|
||||
forceRender = false,
|
||||
path: pathFromProps,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
tabs = [],
|
||||
} = props
|
||||
|
||||
const { indexPath, path: pathFromContext, permissions, schemaPath } = useFieldProps()
|
||||
const {
|
||||
indexPath,
|
||||
path: pathFromContext,
|
||||
permissions,
|
||||
readOnly: readOnlyFromContext,
|
||||
schemaPath,
|
||||
} = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
const path = pathFromContext || pathFromProps || name
|
||||
const { getPreference, setPreference } = usePreferences()
|
||||
const { preferencesKey } = useDocumentInfo()
|
||||
|
||||
@@ -39,6 +39,7 @@ const TextField: React.FC<TextFieldProps> = (props) => {
|
||||
minRows,
|
||||
path: pathFromProps,
|
||||
placeholder,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
rtl,
|
||||
style,
|
||||
@@ -58,9 +59,10 @@ const TextField: React.FC<TextFieldProps> = (props) => {
|
||||
[validate, minLength, maxLength, required],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { path, readOnly, setValue, showError, value } = useField({
|
||||
const { formProcessing, path, setValue, showError, value } = useField({
|
||||
path: pathFromContext || pathFromProps || name,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
@@ -137,7 +139,7 @@ const TextField: React.FC<TextFieldProps> = (props) => {
|
||||
}
|
||||
path={path}
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
readOnly={formProcessing || readOnly}
|
||||
required={required}
|
||||
rtl={renderRTL}
|
||||
showError={showError}
|
||||
|
||||
@@ -36,6 +36,7 @@ const TextareaField: React.FC<TextareaFieldProps> = (props) => {
|
||||
minLength,
|
||||
path: pathFromProps,
|
||||
placeholder,
|
||||
readOnly: readOnlyFromProps,
|
||||
required,
|
||||
rows,
|
||||
rtl,
|
||||
@@ -63,9 +64,10 @@ const TextareaField: React.FC<TextareaFieldProps> = (props) => {
|
||||
[validate, required, maxLength, minLength],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { path, readOnly, setValue, showError, value } = useField<string>({
|
||||
const { path, setValue, showError, value } = useField<string>({
|
||||
path: pathFromContext || pathFromProps || name,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@ const _Upload: React.FC<UploadFieldProps> = (props) => {
|
||||
label,
|
||||
labelProps,
|
||||
path: pathFromProps,
|
||||
readOnly,
|
||||
readOnly: readOnlyFromProps,
|
||||
relationTo,
|
||||
required,
|
||||
style,
|
||||
@@ -50,7 +50,8 @@ const _Upload: React.FC<UploadFieldProps> = (props) => {
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
||||
const readOnly = readOnlyFromProps || readOnlyFromContext
|
||||
|
||||
const { filterOptions, path, setValue, showError, value } = useField<string>({
|
||||
path: pathFromContext || pathFromProps,
|
||||
@@ -77,6 +78,7 @@ const _Upload: React.FC<UploadFieldProps> = (props) => {
|
||||
descriptionProps={descriptionProps}
|
||||
errorProps={errorProps}
|
||||
filterOptions={filterOptions}
|
||||
label={label}
|
||||
labelProps={labelProps}
|
||||
onChange={onChange}
|
||||
path={path}
|
||||
|
||||
@@ -2,5 +2,5 @@ import type { Tenant } from '../payload-types.js'
|
||||
|
||||
export const tenant1: Omit<Tenant, 'createdAt' | 'id' | 'updatedAt'> = {
|
||||
title: 'Tenant 1',
|
||||
clientURL: 'http://localhost:3001',
|
||||
clientURL: 'http://localhost:3000',
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@ import type { Tenant } from '../payload-types.js'
|
||||
|
||||
export const tenant2: Omit<Tenant, 'createdAt' | 'id' | 'updatedAt'> = {
|
||||
title: 'Tenant 2',
|
||||
clientURL: 'http://localhost:3002',
|
||||
clientURL: 'http://localhost:3001',
|
||||
}
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
export const formatLivePreviewURL = async ({ data, documentInfo }) => {
|
||||
import type { LivePreviewConfig } from 'payload/config'
|
||||
|
||||
import type { Tenant } from '../payload-types.js'
|
||||
|
||||
export const formatLivePreviewURL: LivePreviewConfig['url'] = async ({
|
||||
data,
|
||||
collectionConfig,
|
||||
payload,
|
||||
}) => {
|
||||
let baseURL = 'http://localhost:3000/live-preview'
|
||||
|
||||
// You can run async requests here, if needed
|
||||
// For example, multi-tenant apps may need to lookup additional data
|
||||
if (data.tenant) {
|
||||
try {
|
||||
const fullTenant = await fetch(
|
||||
`http://localhost:3000/api/tenants?where[id][equals]=${data.tenant}&limit=1&depth=0`,
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((res) => res?.docs?.[0])
|
||||
const fullTenant = (await payload
|
||||
.find({
|
||||
collection: 'tenants',
|
||||
where: {
|
||||
id: {
|
||||
equals: data.tenant,
|
||||
},
|
||||
},
|
||||
limit: 1,
|
||||
depth: 0,
|
||||
})
|
||||
.then((res) => res?.docs?.[0])) as Tenant
|
||||
|
||||
baseURL = fullTenant?.clientURL
|
||||
if (fullTenant?.clientURL) {
|
||||
baseURL = `${fullTenant.clientURL}/live-preview`
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@@ -21,6 +38,6 @@ export const formatLivePreviewURL = async ({ data, documentInfo }) => {
|
||||
// I.e. append '/posts' to the URL if the document is a post
|
||||
// You can also do this on individual collection or global config, if preferred
|
||||
return `${baseURL}${
|
||||
documentInfo?.slug && documentInfo.slug !== 'pages' ? `/${documentInfo.slug}` : ''
|
||||
collectionConfig && collectionConfig.slug !== 'pages' ? `/${collectionConfig.slug}` : ''
|
||||
}${data?.slug && data.slug !== 'home' ? `/${data.slug}` : ''}`
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"./packages/payload/src/exports/types.ts"
|
||||
],
|
||||
"@payload-config": [
|
||||
"./test/fields/config.ts"
|
||||
"./test/live-preview/config.ts"
|
||||
],
|
||||
"@payloadcms/live-preview": [
|
||||
"./packages/live-preview/src"
|
||||
|
||||
Reference in New Issue
Block a user