Compare commits

..

9 Commits

61 changed files with 349 additions and 270 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.18",
"private": true,
"workspaces:": [
"packages/*"
@@ -9,7 +9,7 @@
"build": "pnpm run build:core",
"build:all": "turbo build",
"build:core": "turbo build --filter \"!@payloadcms/plugin-*\"",
"build:plugins": "turbo build --filter \"@payloadcms/plugin-*\"",
"build:plugins": "turbo build --filter \"@payloadcms/plugin-*\" --filter \"!@payloadcms/plugin-search\"",
"build:app": "next build",
"build:create-payload-app": "turbo build --filter create-payload-app",
"build:db-mongodb": "turbo build --filter db-mongodb",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.18",
"description": "The officially supported MongoDB database adapter for Payload - Update 2",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.7.0",
"version": "3.0.0-alpha.18",
"description": "The officially supported Postgres database adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.18",
"main": "./src/index.ts",
"types": "./src/index.d.ts",
"scripts": {

View File

@@ -10,6 +10,6 @@
}
},
"module": {
"type": "commonjs"
"type": "es6"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.18",
"main": "./src/index.ts",
"types": "./src/index.d.ts",
"bin": {

View File

@@ -15,6 +15,7 @@ import { getRequestLanguage } from '../../utilities/getRequestLanguage'
import { DefaultEditView } from '../../views/Edit/Default'
import { DefaultListView } from '../../views/List/Default'
import { DefaultCell } from '../../views/List/Default/Cell'
import { getPayload } from '../../utilities/getPayload'
export const metadata = {
description: 'Generated by Next.js',
@@ -34,7 +35,13 @@ export const RootLayout = async ({
const clientConfig = await createClientConfig(config)
const headers = getHeaders()
const cookies = parseCookies(headers)
const payload = await getPayload({ config: configPromise })
const { cookies, user, permissions } = await auth({
payload,
headers,
})
const lang =
getRequestLanguage({
@@ -56,6 +63,7 @@ export const RootLayout = async ({
DefaultEditView,
DefaultListView,
config,
permissions: permissions,
})
return (

View File

@@ -16,6 +16,7 @@ export const getPayload = async (options: InitOptions): Promise<Payload> => {
const config = await options.config
if (cached.reload) {
cached.reload = false
if (typeof cached.payload.db.destroy === 'function') {
await cached.payload.db.destroy()
}
@@ -32,7 +33,6 @@ export const getPayload = async (options: InitOptions): Promise<Payload> => {
await cached.payload.db.init()
await cached.payload.db.connect({ hotReload: true })
cached.reload = false
}
return cached.payload
@@ -49,7 +49,7 @@ export const getPayload = async (options: InitOptions): Promise<Payload> => {
try {
const ws = new WebSocket('ws://localhost:3000/_next/webpack-hmr')
ws.onmessage = async (event) => {
ws.onmessage = (event) => {
if (typeof event.data === 'string') {
const data = JSON.parse(event.data)

View File

@@ -10,5 +10,10 @@ export const CreateFirstUserFields: React.FC<{
const fieldMap = getFieldMap({ collectionSlug: userSlug })
return <RenderFields fieldMap={[...(fieldMap || []), ...(createFirstUserFieldMap || [])]} />
return (
<RenderFields
fieldMap={[...(fieldMap || []), ...(createFirstUserFieldMap || [])]}
operation="create"
/>
)
}

View File

@@ -48,10 +48,8 @@ export const CreateFirstUser: React.FC<AdminViewProps> = async ({ initPageResult
const createFirstUserFieldMap = mapFields({
fieldSchema: fields,
operation: 'create',
config,
parentPath: userSlug,
permissions: {},
})
const formState = await buildStateFromSchema({

View File

@@ -15,6 +15,7 @@ import {
import React, { Fragment, useEffect, useState } from 'react'
import './index.scss'
import { Permissions } from 'payload/auth'
const baseClass = 'dashboard'
@@ -22,7 +23,8 @@ export const DefaultDashboardClient: React.FC<{
Link: React.ComponentType
visibleCollections: string[]
visibleGlobals: string[]
}> = ({ Link, visibleCollections, visibleGlobals }) => {
permissions: Permissions
}> = ({ Link, visibleCollections, visibleGlobals, permissions }) => {
const config = useConfig()
const {
@@ -31,7 +33,7 @@ export const DefaultDashboardClient: React.FC<{
routes: { admin },
} = config
const { permissions, user } = useAuth()
const { user } = useAuth()
const { i18n, t } = useTranslation()

View File

@@ -5,6 +5,7 @@ import React from 'react'
import { DefaultDashboardClient } from './index.client'
import './index.scss'
import { Permissions } from 'payload/auth'
const baseClass = 'dashboard'
@@ -13,6 +14,7 @@ export type DashboardProps = {
config: SanitizedConfig
visibleCollections: string[]
visibleGlobals: string[]
permissions: Permissions
}
export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
@@ -25,6 +27,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
},
visibleCollections,
visibleGlobals,
permissions,
} = props
return (
@@ -38,6 +41,7 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
Link={Link}
visibleCollections={visibleCollections}
visibleGlobals={visibleGlobals}
permissions={permissions}
/>
{Array.isArray(afterDashboard) &&
afterDashboard.map((Component, i) => <Component key={i} />)}

View File

@@ -43,6 +43,7 @@ export const Dashboard: React.FC<AdminViewProps> = ({
config,
visibleCollections,
visibleGlobals,
permissions,
}
return (

View File

@@ -38,8 +38,6 @@ export const getViewsFromConfig = async ({
let DefaultView: EditViewComponent = null
let CustomView: EditViewComponent = null
const [entityType, entitySlug, createOrID, tabViewName, segmentFive] = routeSegments
const views =
(collectionConfig && collectionConfig?.admin?.components?.views) ||
(globalConfig && globalConfig?.admin?.components?.views)
@@ -51,6 +49,9 @@ export const getViewsFromConfig = async ({
config?.admin?.livePreview?.globals?.includes(globalConfig?.slug)
if (collectionConfig) {
const [collectionEntity, collectionSlug, createOrID, nestedViewSlug, segmentFive] =
routeSegments
const {
admin: { hidden },
} = collectionConfig
@@ -60,7 +61,7 @@ export const getViewsFromConfig = async ({
}
// `../:id`, or `../create`
if (!tabViewName) {
if (!nestedViewSlug) {
switch (createOrID) {
case 'create': {
if ('create' in docPermissions && docPermissions?.create?.permission) {
@@ -79,10 +80,10 @@ export const getViewsFromConfig = async ({
}
}
if (tabViewName) {
if (nestedViewSlug) {
// `../:id/versions/:version`, etc
if (segmentFive) {
if (tabViewName === 'versions') {
if (nestedViewSlug === 'versions') {
if (docPermissions?.readVersions?.permission) {
CustomView = getCustomViewByKey(views, 'Version')
DefaultView = DefaultVersionView
@@ -92,7 +93,7 @@ export const getViewsFromConfig = async ({
// `../:id/api`, `../:id/preview`, `../:id/versions`, etc
if (routeSegments?.length === 4) {
switch (tabViewName) {
switch (nestedViewSlug) {
case 'api': {
if (collectionConfig?.admin?.hideAPIURL !== true) {
CustomView = getCustomViewByKey(views, 'API')
@@ -117,7 +118,7 @@ export const getViewsFromConfig = async ({
}
default: {
const path = `/${tabViewName}`
const path = `/${nestedViewSlug}`
CustomView = getCustomViewByPath(views, path)
break
}
@@ -127,6 +128,8 @@ export const getViewsFromConfig = async ({
}
if (globalConfig) {
const [globalEntity, globalSlug, nestedViewSlug] = routeSegments
const {
admin: { hidden },
} = globalConfig
@@ -135,14 +138,14 @@ export const getViewsFromConfig = async ({
return null
}
if (!routeSegments?.length) {
if (routeSegments?.length === 2) {
if (docPermissions?.read?.permission) {
CustomView = getCustomViewByKey(views, 'Default')
DefaultView = DefaultEditView
}
} else if (routeSegments?.length === 1) {
} else if (routeSegments?.length === 3) {
// `../:slug/api`, `../:slug/preview`, `../:slug/versions`, etc
switch (tabViewName) {
switch (nestedViewSlug) {
case 'api': {
if (globalConfig?.admin?.hideAPIURL !== true) {
CustomView = getCustomViewByKey(views, 'API')
@@ -176,7 +179,7 @@ export const getViewsFromConfig = async ({
}
} else if (routeSegments?.length === 2) {
// `../:slug/versions/:version`, etc
if (tabViewName === 'versions') {
if (nestedViewSlug === 'versions') {
if (docPermissions?.readVersions?.permission) {
CustomView = getCustomViewByKey(views, 'Version')
DefaultView = DefaultVersionView

View File

@@ -59,7 +59,7 @@ export const Document: React.FC<AdminViewProps> = async ({
const segments = Array.isArray(params?.segments) ? params.segments : []
const [entityType, entitySlug, createOrID] = segments
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
const globalSlug = entitySlug === 'globals' ? entitySlug : undefined
const globalSlug = entityType === 'globals' ? entitySlug : undefined
const isCreating = createOrID === 'create'
const id = (collectionSlug && !isCreating && createOrID) || undefined

View File

@@ -67,7 +67,7 @@ export const DefaultEditView: React.FC = () => {
const globalConfig = globalSlug && globals.find((global) => global.slug === globalSlug)
const [schemaPath] = React.useState(collectionConfig?.slug || globalConfig?.slug)
const schemaPath = collectionConfig?.slug || globalConfig?.slug
const fieldMap = getFieldMap({
collectionSlug: collectionConfig?.slug,
@@ -158,17 +158,6 @@ export const DefaultEditView: React.FC = () => {
}`}
type="withoutNav"
/>
{/* <Meta
description={`${isEditing ? t('general:editing') : t('general:creating')} - ${getTranslation(
collection.labels.singular,
i18n,
)}`}
keywords={`${getTranslation(collection.labels.singular, i18n)}, Payload, CMS`}
title={`${isEditing ? t('general:editing') : t('general:creating')} - ${getTranslation(
collection.labels.singular,
i18n,
)}`}
/> */}
{BeforeDocument}
{preventLeaveWithoutSaving && <LeaveWithoutSaving />}
<SetStepNav

View File

@@ -16,9 +16,11 @@ import { useCallback } from 'react'
export const EditViewClient: React.FC<EditViewProps> = () => {
const { id, collectionSlug, getDocPermissions, getVersions, globalSlug, setDocumentInfo } =
useDocumentInfo()
const {
routes: { admin: adminRoute },
} = useConfig()
const router = useRouter()
const { getComponentMap } = useComponentMap()
@@ -32,8 +34,8 @@ export const EditViewClient: React.FC<EditViewProps> = () => {
const onSave = useCallback(
async (json: { doc }) => {
getVersions()
getDocPermissions()
void getVersions()
void getDocPermissions()
if (!isEditing) {
router.push(`${adminRoute}/collections/${collectionSlug}/${json?.doc?.id}`)

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.18",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",

View File

@@ -125,6 +125,7 @@ export const createClientConfig = async (
delete clientConfig.db
delete clientConfig.editor
delete clientConfig.plugins
delete clientConfig.sharp
'localization' in clientConfig &&
clientConfig.localization &&

View File

@@ -1,7 +1,7 @@
{
"name": "@payloadcms/plugin-cloud-storage",
"description": "The official cloud storage plugin for Payload CMS",
"version": "1.1.2",
"version": "3.0.0-alpha.18",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",

View File

@@ -1,7 +1,7 @@
{
"name": "@payloadcms/plugin-cloud",
"description": "The official Payload Cloud plugin",
"version": "3.0.0",
"version": "3.0.0-alpha.18",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
@@ -10,7 +10,7 @@
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"clean": "rimraf {dist,*.tsbuildinfo} && rimraf dev/yarn.lock",
"prepublishOnly": "pnpm clean && pnpm turbo build && pnpm test",
"prepublishOnly": "pnpm clean && pnpm turbo build",
"test": "jest"
},
"peerDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "2.2.1",
"version": "3.0.0-alpha.18",
"homepage:": "https://payloadcms.com",
"repository": "git@github.com:payloadcms/plugin-seo.git",
"description": "SEO plugin for Payload",

View File

@@ -1,7 +1,6 @@
'use client'
import type { FieldType, Options } from '@payloadcms/ui'
import type { TextareaField } from 'payload/types'
import type { FieldType, FormFieldBase, Options } from '@payloadcms/ui'
import { useFieldPath } from '@payloadcms/ui'
import { useTranslation } from '@payloadcms/ui'
@@ -9,7 +8,7 @@ import { TextareaInput } from '@payloadcms/ui'
import { useAllFormFields, useDocumentInfo, useField, useLocale } from '@payloadcms/ui'
import React, { useCallback } from 'react'
import type { PluginConfig } from '../types'
import type { GenerateDescription } from '../types'
import { defaults } from '../defaults'
import { LengthIndicator } from '../ui/LengthIndicator'
@@ -17,13 +16,13 @@ import { LengthIndicator } from '../ui/LengthIndicator'
const { maxLength, minLength } = defaults.description
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
type MetaDescriptionProps = TextareaField & {
type MetaDescriptionProps = FormFieldBase & {
hasGenerateDescriptionFn: boolean
path: string
pluginConfig: PluginConfig
}
export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
const { name, label, path, pluginConfig, required } = props
const { Label, hasGenerateDescriptionFn, path, required } = props
const { path: pathFromContext, schemaPath } = useFieldPath()
const { t } = useTranslation()
@@ -33,27 +32,31 @@ export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
const docInfo = useDocumentInfo()
const field: FieldType<string> = useField({
name,
label,
path,
} as Options)
const { errorMessage, setValue, showError, value } = field
const regenerateDescription = useCallback(async () => {
/*const { generateDescription } = pluginConfig
let generatedDescription
if (!hasGenerateDescriptionFn) return
if (typeof generateDescription === 'function') {
generatedDescription = await generateDescription({
const genDescriptionResponse = await fetch('/api/plugin-seo/generate-description', {
body: JSON.stringify({
...docInfo,
doc: { ...fields },
locale: typeof locale === 'object' ? locale?.code : locale,
})
}
} satisfies Parameters<GenerateDescription>[0]),
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
})
setValue(generatedDescription)*/
}, [fields, setValue, pluginConfig, locale, docInfo])
const { result: generatedDescription } = await genDescriptionResponse.json()
setValue(generatedDescription || '')
}, [fields, setValue, hasGenerateDescriptionFn, locale, docInfo])
return (
<div
@@ -67,8 +70,8 @@ export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
position: 'relative',
}}
>
<div>
{label && typeof label === 'string' && label}
<div className="plugin-seo__field">
{Label}
{required && (
<span
@@ -81,7 +84,7 @@ export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
</span>
)}
{typeof pluginConfig?.generateDescription === 'function' && (
{hasGenerateDescriptionFn && (
<React.Fragment>
&nbsp; &mdash; &nbsp;
<button
@@ -126,7 +129,7 @@ export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
<TextareaInput
Error={errorMessage} // TODO: Fix
onChange={setValue}
path={name || pathFromContext}
path={pathFromContext}
required={required}
showError={showError}
style={{
@@ -147,7 +150,3 @@ export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
</div>
)
}
export const getMetaDescriptionField = (props: MetaDescriptionProps) => (
<MetaDescription {...props} />
)

View File

@@ -13,18 +13,17 @@ import {
} from '@payloadcms/ui'
import React, { useCallback } from 'react'
import type { PluginConfig } from '../types'
import type { GenerateImage } from '../types'
import { Pill } from '../ui/Pill'
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
type MetaImageProps = UploadInputProps & {
path: string
pluginConfig: PluginConfig
hasGenerateImageFn: boolean
}
export const MetaImage: React.FC<MetaImageProps> = (props) => {
const { label, pluginConfig, relationTo, required } = props || {}
const { Label, hasGenerateImageFn, path, relationTo, required } = props || {}
const field: FieldType<string> = useField(props as Options)
@@ -37,19 +36,25 @@ export const MetaImage: React.FC<MetaImageProps> = (props) => {
const { errorMessage, setValue, showError, value } = field
const regenerateImage = useCallback(async () => {
/*const { generateImage } = pluginConfig
let generatedImage
if (!hasGenerateImageFn) return
if (typeof generateImage === 'function') {
generatedImage = await generateImage({
const genImageResponse = await fetch('/api/plugin-seo/generate-image', {
body: JSON.stringify({
...docInfo,
doc: { ...fields },
locale: typeof locale === 'object' ? locale?.code : locale,
})
}
} satisfies Parameters<GenerateImage>[0]),
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
})
setValue(generatedImage)*/
}, [fields, setValue, pluginConfig, locale, docInfo])
const { result: generatedImage } = await genImageResponse.json()
setValue(generatedImage || '')
}, [fields, setValue, hasGenerateImageFn, locale, docInfo])
const hasImage = Boolean(value)
@@ -71,8 +76,8 @@ export const MetaImage: React.FC<MetaImageProps> = (props) => {
position: 'relative',
}}
>
<div>
{label && typeof label === 'string' && label}
<div className="plugin-seo__field">
{Label}
{required && (
<span
@@ -85,7 +90,7 @@ export const MetaImage: React.FC<MetaImageProps> = (props) => {
</span>
)}
{typeof pluginConfig?.generateImage === 'function' && (
{hasGenerateImageFn && (
<React.Fragment>
&nbsp; &mdash; &nbsp;
<button
@@ -106,7 +111,7 @@ export const MetaImage: React.FC<MetaImageProps> = (props) => {
</React.Fragment>
)}
</div>
{typeof pluginConfig?.generateImage === 'function' && (
{hasGenerateImageFn && (
<div
style={{
color: '#9A9A9A',
@@ -162,5 +167,3 @@ export const MetaImage: React.FC<MetaImageProps> = (props) => {
</div>
)
}
export const getMetaImageField = (props: MetaImageProps) => <MetaImage {...props} />

View File

@@ -1,7 +1,6 @@
'use client'
import type { FieldType, Options } from '@payloadcms/ui'
import type { TextField as TextFieldType } from 'payload/types'
import type { FieldType, FormFieldBase, Options } from '@payloadcms/ui'
import { useFieldPath } from '@payloadcms/ui'
import {
@@ -14,29 +13,26 @@ import {
} from '@payloadcms/ui'
import React, { useCallback } from 'react'
import type { PluginConfig } from '../types'
import type { GenerateTitle } from '../types'
import { defaults } from '../defaults'
import { LengthIndicator } from '../ui/LengthIndicator'
import './index.scss'
const { maxLength, minLength } = defaults.title
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
type MetaTitleProps = TextFieldType & {
path: string
pluginConfig: PluginConfig
type MetaTitleProps = FormFieldBase & {
hasGenerateTitleFn: boolean
}
export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
const { name, label, path, pluginConfig, required } = props || {}
console.log('props tit', props)
const { Label, hasGenerateTitleFn, path, required } = props || {}
const { path: pathFromContext, schemaPath } = useFieldPath()
const { t } = useTranslation()
const field: FieldType<string> = useField({
name,
label,
path,
} as Options)
@@ -47,19 +43,25 @@ export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
const { errorMessage, setValue, showError, value } = field
const regenerateTitle = useCallback(async () => {
/* const { generateTitle } = pluginConfig
let generatedTitle
if (!hasGenerateTitleFn) return
if (typeof generateTitle === 'function') {
generatedTitle = await generateTitle({
const genTitleResponse = await fetch('/api/plugin-seo/generate-title', {
body: JSON.stringify({
...docInfo,
doc: { ...fields },
locale: typeof locale === 'object' ? locale?.code : locale,
})
}
} satisfies Parameters<GenerateTitle>[0]),
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
})
setValue(generatedTitle)*/
}, [fields, setValue, pluginConfig, locale, docInfo])
const { result: generatedTitle } = await genTitleResponse.json()
setValue(generatedTitle || '')
}, [fields, setValue, hasGenerateTitleFn, locale, docInfo])
return (
<div
@@ -73,8 +75,8 @@ export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
position: 'relative',
}}
>
<div>
{label && typeof label === 'string' && label}
<div className="plugin-seo__field">
{Label}
{required && (
<span
@@ -87,7 +89,7 @@ export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
</span>
)}
{typeof pluginConfig?.generateTitle === 'function' && (
{hasGenerateTitleFn && (
<React.Fragment>
&nbsp; &mdash; &nbsp;
<button
@@ -133,7 +135,7 @@ export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
<TextInput
Error={errorMessage} // TODO: fix errormessage
onChange={setValue}
path={name || pathFromContext}
path={pathFromContext}
required={required}
showError={showError}
style={{
@@ -154,5 +156,3 @@ export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
</div>
)
}
export const getMetaTitleField = (props: MetaTitleProps) => <MetaTitle {...props} />

View File

@@ -0,0 +1,5 @@
.plugin-seo__field {
.field-label {
display: inline;
}
}

View File

@@ -4,14 +4,20 @@ import type { Field, GroupField, TabsField, TextField } from 'payload/types'
import { deepMerge } from 'payload/utilities'
import React from 'react'
import type { PluginConfig } from './types'
import type {
GenerateDescription,
GenerateImage,
GenerateTitle,
GenerateURL,
PluginConfig,
} from './types'
import { MetaDescription, getMetaDescriptionField } from './fields/MetaDescription'
import { MetaImage, getMetaImageField } from './fields/MetaImage'
import { MetaTitle, getMetaTitleField } from './fields/MetaTitle'
import { MetaDescription } from './fields/MetaDescription'
import { MetaImage } from './fields/MetaImage'
import { MetaTitle } from './fields/MetaTitle'
import translations from './translations'
import { Overview } from './ui/Overview'
import { Preview, getPreviewField } from './ui/Preview'
import { Preview } from './ui/Preview'
const seo =
(pluginConfig: PluginConfig) =>
@@ -36,22 +42,30 @@ const seo =
type: 'text',
admin: {
components: {
Field: (props) => {
return <MetaTitle {...props} />
},
Field: (props) => (
<MetaTitle
{...props}
hasGenerateTitleFn={typeof pluginConfig.generateTitle === 'function'}
/>
),
},
},
localized: true,
...((pluginConfig?.fieldOverrides?.title as TextField) ?? {}),
...((pluginConfig?.fieldOverrides?.title as unknown as TextField) ?? {}),
},
{
name: 'description',
type: 'textarea',
admin: {
components: {
Field: (props) => {
return <MetaDescription {...props} />
},
Field: (props) => (
<MetaDescription
{...props}
hasGenerateDescriptionFn={
typeof pluginConfig.generateDescription === 'function'
}
/>
),
},
},
localized: true,
@@ -65,9 +79,12 @@ const seo =
type: 'upload',
admin: {
components: {
Field: (props) => {
return <MetaImage {...props} />
},
Field: (props) => (
<MetaImage
{...props}
hasGenerateImageFn={typeof pluginConfig.generateImage === 'function'}
/>
),
},
description:
'Maximum upload file size: 12MB. Recommended file size for images is <500KB.',
@@ -85,9 +102,12 @@ const seo =
type: 'ui',
admin: {
components: {
Field: (props) => {
return <Preview {...props} />
},
Field: (props) => (
<Preview
{...props}
hasGenerateURLFn={typeof pluginConfig.generateURL === 'function'}
/>
),
},
},
label: 'Preview',
@@ -159,6 +179,48 @@ const seo =
return collection
}) || [],
endpoints: [
{
handler: async (req) => {
const args: Parameters<GenerateTitle>[0] =
req.data as unknown as Parameters<GenerateTitle>[0]
const result = await pluginConfig.generateTitle(args)
return new Response(JSON.stringify({ result }), { status: 200 })
},
method: 'post',
path: '/plugin-seo/generate-title',
},
{
handler: async (req) => {
const args: Parameters<GenerateDescription>[0] =
req.data as unknown as Parameters<GenerateDescription>[0]
const result = await pluginConfig.generateDescription(args)
return new Response(JSON.stringify({ result }), { status: 200 })
},
method: 'post',
path: '/plugin-seo/generate-description',
},
{
handler: async (req) => {
const args: Parameters<GenerateURL>[0] =
req.data as unknown as Parameters<GenerateURL>[0]
const result = await pluginConfig.generateURL(args)
return new Response(JSON.stringify({ result }), { status: 200 })
},
method: 'post',
path: '/plugin-seo/generate-url',
},
{
handler: async (req) => {
const args: Parameters<GenerateImage>[0] =
req.data as unknown as Parameters<GenerateImage>[0]
const result = await pluginConfig.generateImage(args)
return new Response(result, { status: 200 })
},
method: 'post',
path: '/plugin-seo/generate-image',
},
],
globals:
config.globals?.map((global) => {
const { slug } = global

View File

@@ -5,16 +5,14 @@ import type { FormField, UIField } from 'payload/types'
import { useAllFormFields, useDocumentInfo, useLocale, useTranslation } from '@payloadcms/ui'
import React, { useEffect, useState } from 'react'
import type { PluginConfig } from '../types'
import type { GenerateURL } from '../types'
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
type PreviewProps = UIField & {
pluginConfig: PluginConfig
hasGenerateURLFn: boolean
}
export const Preview: React.FC<PreviewProps> = (props) => {
const { pluginConfig: { generateURL } = {} } = props || {}
export const Preview: React.FC<PreviewProps> = ({ hasGenerateURLFn }) => {
const { t } = useTranslation()
const locale = useLocale()
@@ -29,20 +27,29 @@ export const Preview: React.FC<PreviewProps> = (props) => {
const [href, setHref] = useState<string>()
useEffect(() => {
/* const getHref = async () => {
if (typeof generateURL === 'function' && !href) {
const newHref = await generateURL({
const getHref = async () => {
const genURLResponse = await fetch('/api/plugin-seo/generate-url', {
body: JSON.stringify({
...docInfo,
doc: { ...fields },
locale: typeof locale === 'object' ? locale?.code : locale,
})
} satisfies Parameters<GenerateURL>[0]),
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
})
setHref(newHref)
}
const { result: newHref } = await genURLResponse.json()
setHref(newHref)
}
getHref() // eslint-disable-line @typescript-eslint/no-floating-promises*/
}, [generateURL, fields, href, locale, docInfo])
if (hasGenerateURLFn && !href) {
void getHref()
}
}, [fields, href, locale, docInfo, hasGenerateURLFn])
return (
<div>
@@ -101,5 +108,3 @@ export const Preview: React.FC<PreviewProps> = (props) => {
</div>
)
}
export const getPreviewField = (props: PreviewProps) => <Preview {...props} />

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "0.7.0",
"version": "3.0.0-alpha.18",
"description": "The officially supported Lexical richtext adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -82,7 +82,6 @@ export const getGenerateComponentMap =
const mappedFields = mapFields({
config,
fieldSchema: sanitizedFields,
operation: 'update',
permissions: {},
readOnly: false,
})

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-slate",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.18",
"description": "The officially supported Slate richtext adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -70,7 +70,7 @@ export const getGenerateComponentMap =
switch (element.name) {
case 'link': {
const linkFields = sanitizeFields({
config: config,
config,
fields: transformExtraFields(args.admin?.link?.fields, config, i18n),
validRelationships,
})
@@ -78,7 +78,6 @@ export const getGenerateComponentMap =
const mappedFields = mapFields({
config,
fieldSchema: linkFields,
operation: 'update',
permissions: {},
readOnly: false,
})
@@ -110,7 +109,6 @@ export const getGenerateComponentMap =
const mappedFields = mapFields({
config,
fieldSchema: uploadFields,
operation: 'update',
permissions: {},
readOnly: false,
})

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/translations",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.18",
"main": "./dist/exports/index.ts",
"types": "./dist/types.d.ts",
"scripts": {

View File

@@ -79,7 +79,8 @@ const replaceVars = ({
.map((part) => {
if (part.startsWith('{{') && part.endsWith('}}')) {
const placeholder = part.substring(2, part.length - 2).trim()
return vars[placeholder] || part
const value = vars[placeholder]
return value !== undefined && value !== null ? value : part
} else {
return part
}

View File

@@ -10,6 +10,6 @@
}
},
"module": {
"type": "commonjs"
"type": "es6"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/ui",
"version": "3.0.0-alpha.15",
"version": "3.0.0-alpha.18",
"main": "./src/index.ts",
"types": "./dist/index.d.ts",
"scripts": {

View File

@@ -1,5 +1,5 @@
'use client'
import type { Description } from 'payload/types'
import type { Description, Operation } from 'payload/types'
import React from 'react'

View File

@@ -1,12 +0,0 @@
@import '../../scss/styles.scss';
.field-description {
display: flex;
color: var(--theme-elevation-400);
margin-top: calc(var(--base) / 4);
&--margin-bottom {
margin-top: 0;
margin-bottom: calc(var(--base) / 2);
}
}

View File

@@ -5,6 +5,7 @@ type FieldPathContextType = {
path: string
schemaPath: string
}
const FieldPathContext = React.createContext<FieldPathContextType>({
path: '',
schemaPath: '',

View File

@@ -1,6 +0,0 @@
export type Props = {
className?: string
description?: string
marginPlacement?: 'bottom' | 'top'
value?: unknown
}

View File

@@ -556,7 +556,7 @@ const Form: React.FC<Props> = (props) => {
}
}
executeOnChange() // eslint-disable-line @typescript-eslint/no-floating-promises
void executeOnChange() // eslint-disable-line @typescript-eslint/no-floating-promises
},
150,
[fields, dispatchFields, onChange],

View File

@@ -1,4 +1,5 @@
'use client'
import * as React from 'react'
import { Banner } from '../../elements/Banner'
@@ -13,6 +14,7 @@ type NullifyLocaleFieldProps = {
localized: boolean
path: string
}
export const NullifyLocaleField: React.FC<NullifyLocaleFieldProps> = ({
fieldValue,
localized,
@@ -30,8 +32,8 @@ export const NullifyLocaleField: React.FC<NullifyLocaleFieldProps> = ({
const useFallback = !checked
dispatchFields({
path,
type: 'UPDATE',
path,
value: useFallback ? null : fieldValue || 0,
})
setModified(true)

View File

@@ -0,0 +1,18 @@
'use client'
import React from 'react'
const ReadOnlyContext = React.createContext<boolean | undefined>(undefined)
export const ReadOnlyProvider: React.FC<{
children: React.ReactNode
readOnly?: boolean
}> = (props) => {
const { children, readOnly } = props
return <ReadOnlyContext.Provider value={readOnly}>{children}</ReadOnlyContext.Provider>
}
export const useReadOnly = () => {
const path = React.useContext(ReadOnlyContext)
return path
}

View File

@@ -1,20 +1,45 @@
'use client'
import type { FieldPermissions } from 'payload/auth'
import React from 'react'
import { useOperation } from '../../providers/OperationProvider'
import { FieldPathProvider, useFieldPath } from '../FieldPathProvider'
import { ReadOnlyProvider, useReadOnly } from '../ReadOnlyProvider'
export const RenderField: React.FC<{
Field: React.ReactNode
fieldPermissions: FieldPermissions
name?: string
readOnly?: boolean
}> = (props) => {
const { name, Field } = props
const { name, Field, fieldPermissions, readOnly: readOnlyFromProps } = props
const { path: pathFromContext, schemaPath: schemaPathFromContext } = useFieldPath()
const readOnlyFromContext = useReadOnly()
const operation = useOperation()
const path = `${pathFromContext ? `${pathFromContext}.` : ''}${name || ''}`
const schemaPath = `${schemaPathFromContext ? `${schemaPathFromContext}.` : ''}${name || ''}`
// `admin.readOnly` displays the value but prevents the field from being edited
let readOnly = readOnlyFromProps
// if parent field is `readOnly: true`, but this field is `readOnly: false`, the field should still be editable
if (readOnlyFromContext && readOnly !== false) readOnly = true
// if the user does not have access control to begin with, force it to be read-only
if (fieldPermissions?.[operation]?.permission === false) {
readOnly = true
}
return (
<FieldPathProvider path={path} schemaPath={schemaPath}>
{Field}
</FieldPathProvider>
<ReadOnlyProvider readOnly={readOnly}>
<FieldPathProvider path={path} schemaPath={schemaPath}>
{Field}
</FieldPathProvider>
</ReadOnlyProvider>
)
}

View File

@@ -53,8 +53,14 @@ const RenderFields: React.FC<Props> = (props) => {
ref={intersectionRef}
>
{hasRendered &&
fieldMap?.map(({ name, Field }, fieldIndex) => (
<RenderField Field={Field} key={fieldIndex} name={name} />
fieldMap?.map(({ name, Field, fieldPermissions, readOnly }, fieldIndex) => (
<RenderField
Field={Field}
fieldPermissions={fieldPermissions}
key={fieldIndex}
name={name}
readOnly={readOnly}
/>
))}
</div>
)

View File

@@ -1,3 +1,5 @@
import type { Operation } from 'payload/types'
import type { FieldMap } from '../../utilities/buildComponentMap/types'
export type Props = {
@@ -5,4 +7,5 @@ export type Props = {
fieldMap: FieldMap
forceRender?: boolean
margins?: 'small' | false
operation?: Operation
}

View File

@@ -1,5 +1,5 @@
import type { FieldPermissions } from 'payload/auth'
import type { ArrayField, Row, RowLabel as RowLabelType } from 'payload/types'
import type { ArrayField, Operation, Row, RowLabel as RowLabelType } from 'payload/types'
import { getTranslation } from '@payloadcms/translations'
import React from 'react'

View File

@@ -105,10 +105,10 @@ const BlocksField: React.FC<Props> = (props) => {
const addRow = useCallback(
async (rowIndex: number, blockType: string) => {
dispatchFields({
type: 'ADD_ROW',
blockType,
path,
rowIndex,
type: 'ADD_ROW',
})
setModified(true)
@@ -117,12 +117,12 @@ const BlocksField: React.FC<Props> = (props) => {
scrollToID(`${path}-row-${rowIndex + 1}`)
}, 0)
},
[path, setModified],
[path, setModified, dispatchFields],
)
const duplicateRow = useCallback(
(rowIndex: number) => {
dispatchFields({ path, rowIndex, type: 'DUPLICATE_ROW' })
dispatchFields({ type: 'DUPLICATE_ROW', path, rowIndex })
setModified(true)
setTimeout(() => {
@@ -135,9 +135,9 @@ const BlocksField: React.FC<Props> = (props) => {
const removeRow = useCallback(
(rowIndex: number) => {
dispatchFields({
type: 'REMOVE_ROW',
path,
rowIndex,
type: 'REMOVE_ROW',
})
setModified(true)
@@ -147,7 +147,7 @@ const BlocksField: React.FC<Props> = (props) => {
const moveRow = useCallback(
(moveFromIndex: number, moveToIndex: number) => {
dispatchFields({ moveFromIndex, moveToIndex, path, type: 'MOVE_ROW' })
dispatchFields({ type: 'MOVE_ROW', moveFromIndex, moveToIndex, path })
setModified(true)
},
[dispatchFields, path, setModified],
@@ -155,14 +155,14 @@ const BlocksField: React.FC<Props> = (props) => {
const toggleCollapseAll = useCallback(
(collapsed: boolean) => {
dispatchFields({ collapsed, path, setDocFieldPreferences, type: 'SET_ALL_ROWS_COLLAPSED' })
dispatchFields({ type: 'SET_ALL_ROWS_COLLAPSED', collapsed, path, setDocFieldPreferences })
},
[dispatchFields, path, setDocFieldPreferences],
)
const setCollapse = useCallback(
(rowID: string, collapsed: boolean) => {
dispatchFields({ collapsed, path, rowID, setDocFieldPreferences, type: 'SET_ROW_COLLAPSED' })
dispatchFields({ type: 'SET_ROW_COLLAPSED', collapsed, path, rowID, setDocFieldPreferences })
},
[dispatchFields, path, setDocFieldPreferences],
)

View File

@@ -2,7 +2,6 @@
import type { DocumentPreferences } from 'payload/types'
import { getTranslation } from '@payloadcms/translations'
import { tabHasName } from 'payload/types'
import { toKebabCase } from 'payload/utilities'
import React, { useCallback, useEffect, useState } from 'react'

View File

@@ -8,7 +8,6 @@ import type { Props } from './types'
import { useConfig } from '../../../providers/Config'
import { useLocale } from '../../../providers/Locale'
import { useTranslation } from '../../../providers/Translation'
import LabelComp from '../../Label'
import useField from '../../useField'
import { withCondition } from '../../withCondition'
@@ -33,10 +32,8 @@ const Text: React.FC<Props> = (props) => {
maxRows,
minLength,
minRows,
onKeyDown,
path: pathFromProps,
placeholder,
readOnly,
required,
rtl,
style,
@@ -46,8 +43,6 @@ const Text: React.FC<Props> = (props) => {
const Label = LabelFromProps || <LabelComp label={label} required={required} />
const { i18n, t } = useTranslation()
const locale = useLocale()
const { localization: localizationConfig } = useConfig()
@@ -60,7 +55,7 @@ const Text: React.FC<Props> = (props) => {
[validate, minLength, maxLength, required],
)
const { path, schemaPath, setValue, showError, value } = useField({
const { path, readOnly, setValue, showError, value } = useField({
path: pathFromProps || name,
validate: memoizedValidate,
})

View File

@@ -31,7 +31,6 @@ const Textarea: React.FC<Props> = (props) => {
minLength,
path: pathFromProps,
placeholder,
readOnly,
required,
rtl,
style,
@@ -62,7 +61,7 @@ const Textarea: React.FC<Props> = (props) => {
[validate, required, maxLength, minLength],
)
const { path, setValue, showError, value } = useField<string>({
const { path, readOnly, setValue, showError, value } = useField<string>({
path: pathFromProps || name,
validate: memoizedValidate,
})

View File

@@ -1,4 +1,4 @@
import type { User } from 'payload/auth'
import type { FieldPermissions, User } from 'payload/auth'
import type { Locale, SanitizedLocalizationConfig } from 'payload/config'
import type {
ArrayField,
@@ -33,6 +33,7 @@ export type FormFieldBase = {
className?: string
docPreferences?: DocumentPreferences
fieldMap?: FieldMap
fieldPermissions?: FieldPermissions
initialSubfieldState?: FormState
label?: string
locale?: Locale

View File

@@ -12,6 +12,7 @@ import { useOperation } from '../../providers/OperationProvider'
import { useTranslation } from '../../providers/Translation'
import { useFieldPath } from '../FieldPathProvider'
import { useForm, useFormFields, useFormProcessing, useFormSubmitted } from '../Form/context'
import { useReadOnly } from '../ReadOnlyProvider'
/**
* Get and set the value of a form field.
@@ -23,6 +24,8 @@ const useField = <T,>(options: Options): FieldType<T> => {
const { path: pathFromContext, schemaPath } = useFieldPath()
const readOnly = useReadOnly()
const path = options.path || pathFromContext
const submitted = useFormSubmitted()
@@ -83,6 +86,7 @@ const useField = <T,>(options: Options): FieldType<T> => {
formSubmitted: submitted,
initialValue,
path,
readOnly: readOnly || false,
rows: field?.rows,
schemaPath,
setValue,
@@ -102,6 +106,7 @@ const useField = <T,>(options: Options): FieldType<T> => {
initialValue,
path,
schemaPath,
readOnly,
],
)

View File

@@ -16,6 +16,7 @@ export type FieldType<T> = {
formSubmitted: boolean
initialValue?: T
path: string
readOnly?: boolean
rows?: Row[]
schemaPath: string
setValue: (val: unknown, disableModifyingForm?: boolean) => void

View File

@@ -1,21 +0,0 @@
'use client'
import type { Permissions, User } from 'payload/auth'
import { useEffect } from 'react'
import { useAuth } from '..'
export const HydrateClientUser: React.FC<{ permissions: Permissions; user: User }> = ({
permissions,
user,
}) => {
const { setPermissions, setUser } = useAuth()
useEffect(() => {
setUser(user)
setPermissions(permissions)
}, [user, permissions, setUser, setPermissions])
return null
}

View File

@@ -159,6 +159,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
const params = {
locale: code,
}
try {
const request = await requests.get(`${serverURL}${api}/access?${qs.stringify(params)}`, {
headers: {
@@ -256,13 +257,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
setLastLocationChange(Date.now())
}, [pathname])
// When user changes, get new access
useEffect(() => {
if (id) {
refreshPermissions()
}
}, [i18n, id, api, serverURL, refreshPermissions])
useEffect(() => {
let reminder: ReturnType<typeof setTimeout>
const now = Math.round(new Date().getTime() / 1000)

View File

@@ -1,4 +1,4 @@
import type { FieldPermissions } from 'payload/auth'
import type { CollectionPermission, GlobalPermission, Permissions } from 'payload/auth'
import type { EditViewProps, SanitizedConfig } from 'payload/types'
import React from 'react'
@@ -13,12 +13,7 @@ export const buildComponentMap = (args: {
DefaultEditView: React.FC<EditViewProps>
DefaultListView: React.FC<EditViewProps>
config: SanitizedConfig
operation?: 'create' | 'update'
permissions?:
| {
[field: string]: FieldPermissions
}
| FieldPermissions
permissions?: Permissions
readOnly?: boolean
}): ComponentMap => {
const {
@@ -26,15 +21,18 @@ export const buildComponentMap = (args: {
DefaultEditView,
DefaultListView,
config,
operation = 'update',
permissions,
readOnly: readOnlyOverride,
} = args
let entityPermissions: CollectionPermission | GlobalPermission
// Collections
const collections = config.collections.reduce((acc, collectionConfig) => {
const { slug, fields } = collectionConfig
entityPermissions = permissions.collections[collectionConfig.slug]
const editViewFromConfig = collectionConfig?.admin?.components?.views?.Edit
const listViewFromConfig = collectionConfig?.admin?.components?.views?.List
@@ -110,8 +108,7 @@ export const buildComponentMap = (args: {
DefaultCell,
config,
fieldSchema: fields,
operation,
permissions,
permissions: entityPermissions.fields,
readOnly: readOnlyOverride,
}),
}
@@ -126,6 +123,8 @@ export const buildComponentMap = (args: {
const globals = config.globals.reduce((acc, globalConfig) => {
const { slug, fields } = globalConfig
entityPermissions = permissions.globals[globalConfig.slug]
const editViewFromConfig = globalConfig?.admin?.components?.views?.Edit
const CustomEditView =
@@ -150,8 +149,7 @@ export const buildComponentMap = (args: {
DefaultCell,
config,
fieldSchema: fields,
operation,
permissions,
permissions: entityPermissions.fields,
readOnly: readOnlyOverride,
}),
}

View File

@@ -1,4 +1,4 @@
import type { FieldPermissions } from 'payload/auth'
import type { CollectionPermission, FieldPermissions, GlobalPermission } from 'payload/auth'
import type { CellProps, Field, FieldWithPath, LabelProps, SanitizedConfig } from 'payload/types'
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/types'
@@ -21,13 +21,8 @@ export const mapFields = (args: {
config: SanitizedConfig
fieldSchema: FieldWithPath[]
filter?: (field: Field) => boolean
operation?: 'create' | 'update'
parentPath?: string
permissions?:
| {
[field: string]: FieldPermissions
}
| FieldPermissions
permissions?: CollectionPermission['fields'] | GlobalPermission['fields']
readOnly?: boolean
}): FieldMap => {
const {
@@ -35,7 +30,6 @@ export const mapFields = (args: {
config,
fieldSchema,
filter,
operation = 'update',
parentPath,
permissions,
readOnly: readOnlyOverride,
@@ -57,26 +51,14 @@ export const mapFields = (args: {
field.path || (isFieldAffectingData && 'name' in field ? field.name : '')
}`
const fieldPermissions = isFieldAffectingData ? permissions?.[field.name] : permissions
const fieldPermissions = isFieldAffectingData ? permissions?.[field.name] : undefined
// if the user cannot read the field, then filter it out
// this is different from `admin.readOnly` which is executed on the client based on `operation`
if (fieldPermissions?.read?.permission === false) {
return acc
}
// readOnly from field config
let readOnly = field.admin && 'readOnly' in field.admin ? field.admin.readOnly : undefined
// if parent field is readOnly
// but this field is `readOnly: false`
// the field should be editable
if (readOnlyOverride && readOnly !== false) readOnly = true
// unless the user does not pass access control
if (fieldPermissions?.[operation]?.permission === false) {
readOnly = true
}
const labelProps: LabelProps = {
htmlFor: 'TODO',
// TODO: fix types
@@ -103,7 +85,6 @@ export const mapFields = (args: {
config,
fieldSchema: field.fields,
filter,
operation,
parentPath: path,
permissions,
readOnly: readOnlyOverride,
@@ -120,7 +101,6 @@ export const mapFields = (args: {
config,
fieldSchema: tab.fields,
filter,
operation,
parentPath: path,
permissions,
readOnly: readOnlyOverride,
@@ -146,7 +126,6 @@ export const mapFields = (args: {
config,
fieldSchema: block.fields,
filter,
operation,
parentPath: `${path}.${block.slug}`,
permissions,
readOnly: readOnlyOverride,
@@ -232,11 +211,14 @@ export const mapFields = (args: {
// TODO: fix types
// label: 'label' in field ? field.label : undefined,
blocks,
fieldPermissions,
hasMany: 'hasMany' in field ? field.hasMany : undefined,
max: 'max' in field ? field.max : undefined,
maxRows: 'maxRows' in field ? field.maxRows : undefined,
min: 'min' in field ? field.min : undefined,
options: 'options' in field ? field.options : undefined,
readOnly:
'admin' in field && 'readOnly' in field.admin ? field.admin.readOnly : undefined,
relationTo: 'relationTo' in field ? field.relationTo : undefined,
richTextComponentMap: undefined,
step: 'admin' in field && 'step' in field.admin ? field.admin.step : undefined,
@@ -353,7 +335,8 @@ export const mapFields = (args: {
labels: 'labels' in field ? field.labels : undefined,
localized: 'localized' in field ? field.localized : false,
options: 'options' in field ? field.options : undefined,
readOnly,
readOnly:
'admin' in field && 'readOnly' in field.admin ? field.admin.readOnly : undefined,
relationTo: 'relationTo' in field ? field.relationTo : undefined,
subfields: nestedFieldMap,
tabs,
@@ -379,7 +362,7 @@ export const mapFields = (args: {
Field: <HiddenInput name="id" />,
Heading: <SortColumn label="ID" name="id" />,
fieldIsPresentational: false,
fieldPermissions: {} as FieldPermissions, // TODO: wire this up
fieldPermissions: {} as FieldPermissions,
isFieldAffectingData: true,
isSidebar: false,
label: 'ID',

View File

@@ -53,6 +53,9 @@ export type MappedField = {
* On `select` fields only
*/
options?: Option[]
/**
* This is the `admin.readOnly` value from the field's config
*/
readOnly: boolean
/**
* On `relationship` fields only

View File

@@ -14,7 +14,7 @@ export default buildConfigWithDefaults({
locales: ['en', 'es', 'de'],
},
i18n: {
resources: {
translations: {
es: {
'plugin-seo': {
autoGenerate: 'Auto-génerar',
@@ -46,7 +46,7 @@ export default buildConfigWithDefaults({
},
},
generateTitle: (data: any) => `Website.com — ${data?.doc?.title?.value}`,
generateDescription: ({ doc }: any) => doc?.excerpt?.value,
generateDescription: ({ doc }: any) => doc?.excerpt?.value || 'generated description',
generateURL: ({ doc, locale }: any) =>
`https://yoursite.com/${locale ? locale + '/' : ''}${doc?.slug?.value || ''}`,
}),

View File

@@ -13,7 +13,7 @@ export const seed = async (payload: Payload): Promise<boolean> => {
try {
// Create image
const filePath = path.resolve(__dirname, '../image-1.jpg')
const filePath = path.resolve(process.cwd(), './test/plugin-seo/image-1.jpg')
const file = await getFileByPath(filePath)
const mediaDoc = await payload.create({