Merge branch 'alpha' of github.com:payloadcms/payload into chore/pre-build-e2e

This commit is contained in:
James
2024-04-05 15:25:41 -04:00
46 changed files with 275 additions and 155 deletions

View File

@@ -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",

View File

@@ -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 && (

View File

@@ -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

View File

@@ -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 (

View File

@@ -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'

View File

@@ -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
}

View 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

View File

@@ -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,

View File

@@ -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',

View File

@@ -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: {

View File

@@ -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,
})

View File

@@ -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

View File

@@ -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,

View File

@@ -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,
}),
}

View File

@@ -37,6 +37,7 @@ export const blockPopulationPromiseHOC = (
block.fields = sanitizeFields({
config: payloadConfig,
fields: block.fields,
requireFieldLevelRichTextEditor: true,
validRelationships,
})
})

View File

@@ -28,6 +28,7 @@ export const blockValidationHOC = (
block.fields = sanitizeFields({
config,
fields: block.fields,
requireFieldLevelRichTextEditor: true,
validRelationships,
})
})

View File

@@ -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> = (

View File

@@ -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(() => {

View File

@@ -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[]
}
}
}

View File

@@ -79,6 +79,7 @@ export const getGenerateComponentMap =
const sanitizedFields = sanitizeFields({
config,
fields: cloneDeep(fields),
requireFieldLevelRichTextEditor: true,
validRelationships,
})

View File

@@ -31,6 +31,7 @@ export const getGenerateSchemaMap =
const sanitizedFields = sanitizeFields({
config,
fields: cloneDeep(fields),
requireFieldLevelRichTextEditor: true,
validRelationships,
})

View File

@@ -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)

View File

@@ -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}
/>

View File

@@ -26,7 +26,7 @@ export const FileCell: React.FC<FileCellProps> = ({
...rowData,
filename,
}}
fileSrc={rowData?.thumbnailURL}
fileSrc={rowData?.thumbnailURL || rowData?.url}
size="small"
uploadConfig={uploadConfig}
/>

View File

@@ -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()

View File

@@ -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()

View File

@@ -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,

View File

@@ -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,

View File

@@ -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} />

View File

@@ -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={[

View File

@@ -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}

View File

@@ -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)

View File

@@ -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,

View File

@@ -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,

View File

@@ -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}`,

View File

@@ -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,

View File

@@ -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[]

View File

@@ -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,

View File

@@ -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()

View File

@@ -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}

View File

@@ -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,
})

View File

@@ -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}

View File

@@ -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',
}

View File

@@ -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',
}

View File

@@ -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}` : ''}`
}

View File

@@ -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"