mirror of
https://github.com/payloadcms/payload.git
synced 2026-09-14 20:07:19 +08:00
a70d39d30b
`generateSlug`'s two operation branches disagreed on how to represent "no
slug yet". The create branch assigned `slugify(...)` unconditionally, so a
draft created before a title exists stored `''` — the admin submits
`title: ''`, and `slugify('')` returns `''`. The update branch already
falls back to `null` for the same case.
A unique index permits many missing values but only one `''`, and draft
saves skip field validation, so `required: true` does not catch it. The
first title-less draft inserted and the second failed with a duplicate key
error, which the admin surfaced as a blank Create New screen. Creating an
empty draft and typing the title afterwards is the default admin flow, so
this was the common path rather than an edge case.
The create branch now leaves an empty slug unset. `undefined` rather than
`null` matches what the unique index skips on both databases: Mongo's index
is sparse once drafts are enabled, and Postgres maps a missing value to
NULL. Storing `null` would have fixed Postgres alone while leaving Mongo
colliding on two nulls.
Adds a `slug-field-autosave` test collection and integration tests covering
a source-less draft, an empty source value, a custom slugify that returns
an empty string, and two source-less drafts coexisting under the unique
index. Verified against MongoDB, Postgres, and SQLite.
176 lines
4.7 KiB
TypeScript
176 lines
4.7 KiB
TypeScript
import type { CollectionConfig, Config } from 'payload'
|
|
|
|
import { fileURLToPath } from 'node:url'
|
|
import path from 'path'
|
|
|
|
const filename = fileURLToPath(import.meta.url)
|
|
const dirname = path.dirname(filename)
|
|
|
|
import ArrayFields from './collections/Array/index.js'
|
|
import { BlockFields } from './collections/Blocks/index.js'
|
|
import CheckboxFields from './collections/Checkbox/index.js'
|
|
import CodeFields from './collections/Code/index.js'
|
|
import CollapsibleFields from './collections/Collapsible/index.js'
|
|
import ConditionalLogic from './collections/ConditionalLogic/index.js'
|
|
import { CustomRowID } from './collections/CustomID/CustomRowID.js'
|
|
import { CustomTabID } from './collections/CustomID/CustomTabID.js'
|
|
import { CustomID } from './collections/CustomID/index.js'
|
|
import { CustomIDNested } from './collections/CustomIDNested/index.js'
|
|
import DateFields from './collections/Date/index.js'
|
|
import EmailFields from './collections/Email/index.js'
|
|
import GroupFields from './collections/Group/index.js'
|
|
import IndexedFields from './collections/Indexed/index.js'
|
|
import JSONFields from './collections/JSON/index.js'
|
|
import NumberFields from './collections/Number/index.js'
|
|
import PointFields from './collections/Point/index.js'
|
|
import RadioFields from './collections/Radio/index.js'
|
|
import RelationshipFields from './collections/Relationship/index.js'
|
|
import RowFields from './collections/Row/index.js'
|
|
import SelectFields from './collections/Select/index.js'
|
|
import SelectVersionsFields from './collections/SelectVersions/index.js'
|
|
import SlugField from './collections/SlugField/index.js'
|
|
import SlugFieldAutosave from './collections/SlugFieldAutosave/index.js'
|
|
import TabsFields from './collections/Tabs/index.js'
|
|
import { TabsFields2 } from './collections/Tabs2/index.js'
|
|
import TextFields from './collections/Text/index.js'
|
|
import TextareaFields from './collections/Textarea/index.js'
|
|
import UIFields from './collections/UI/index.js'
|
|
import Uploads from './collections/Upload/index.js'
|
|
import Uploads2 from './collections/Upload2/index.js'
|
|
import UploadsMulti from './collections/UploadMulti/index.js'
|
|
import UploadsMultiPoly from './collections/UploadMultiPoly/index.js'
|
|
import UploadsPoly from './collections/UploadPoly/index.js'
|
|
import UploadRestricted from './collections/UploadRestricted/index.js'
|
|
import Uploads3 from './collections/Uploads3/index.js'
|
|
import { seed } from './seed.js'
|
|
|
|
export const collections: CollectionConfig[] = [
|
|
{
|
|
slug: 'users',
|
|
admin: {
|
|
useAsTitle: 'email',
|
|
},
|
|
auth: true,
|
|
fields: [
|
|
{
|
|
name: 'canViewConditionalField',
|
|
type: 'checkbox',
|
|
defaultValue: true,
|
|
},
|
|
],
|
|
},
|
|
SelectVersionsFields,
|
|
ArrayFields,
|
|
BlockFields,
|
|
CheckboxFields,
|
|
CodeFields,
|
|
CollapsibleFields,
|
|
ConditionalLogic,
|
|
CustomID,
|
|
CustomIDNested,
|
|
CustomTabID,
|
|
CustomRowID,
|
|
DateFields,
|
|
EmailFields,
|
|
RadioFields,
|
|
GroupFields,
|
|
RowFields,
|
|
IndexedFields,
|
|
JSONFields,
|
|
NumberFields,
|
|
PointFields,
|
|
RelationshipFields,
|
|
SelectFields,
|
|
SlugField,
|
|
SlugFieldAutosave,
|
|
TabsFields2,
|
|
TabsFields,
|
|
TextFields,
|
|
TextareaFields,
|
|
Uploads,
|
|
Uploads2,
|
|
Uploads3,
|
|
UploadsMulti,
|
|
UploadsPoly,
|
|
UploadsMultiPoly,
|
|
UploadRestricted,
|
|
UIFields,
|
|
]
|
|
|
|
export const baseConfig: Partial<Config> = {
|
|
collections,
|
|
blocks: [
|
|
{
|
|
slug: 'ConfigBlockTest',
|
|
fields: [
|
|
{
|
|
name: 'deduplicatedText',
|
|
type: 'text',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
slug: 'localizedTextReference',
|
|
fields: [
|
|
{
|
|
name: 'text',
|
|
type: 'text',
|
|
localized: true,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
slug: 'localizedTextReference2',
|
|
fields: [
|
|
{
|
|
name: 'text',
|
|
type: 'text',
|
|
localized: true,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
custom: {
|
|
client: {
|
|
'new-value': 'client available',
|
|
},
|
|
server: {
|
|
'new-server-value': 'only available on server',
|
|
},
|
|
},
|
|
admin: {
|
|
importMap: {
|
|
baseDir: path.resolve(dirname),
|
|
},
|
|
components: {
|
|
afterNavLinks: ['/components/AfterNavLinks.js#AfterNavLinks'],
|
|
},
|
|
custom: {
|
|
client: {
|
|
'new-value': 'client available',
|
|
},
|
|
},
|
|
timezones: {
|
|
supportedTimezones: ({ defaultTimezones }) => [
|
|
...defaultTimezones,
|
|
{ label: '(GMT-6) Monterrey, Nuevo Leon', value: 'America/Monterrey' },
|
|
{ label: 'Custom UTC', value: 'UTC' },
|
|
],
|
|
defaultTimezone: 'America/Monterrey',
|
|
},
|
|
},
|
|
localization: {
|
|
defaultLocale: 'en',
|
|
fallback: true,
|
|
locales: ['en', 'es'],
|
|
},
|
|
onInit: async (payload) => {
|
|
if (process.env.SEED_IN_CONFIG_ONINIT !== 'false') {
|
|
await seed(payload)
|
|
}
|
|
},
|
|
typescript: {
|
|
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
|
},
|
|
}
|