## Description Currently, you cannot create, delete, or duplicate documents within the document drawer directly. To create a document within a relationship field, for example, you must first navigate to the parent field and open the "create new" drawer. Similarly (but worse), to duplicate or delete a document, you must _navigate to the parent document to perform these actions_ which is incredibly disruptive to the content editing workflow. This becomes especially apparent within the relationship field where you can edit documents inline, but cannot duplicate or delete them. This PR supports all document-level actions within the document drawer so that these actions can be performed on-the-fly without navigating away. Inline duplication flow on a polymorphic "hasOne" relationship: https://github.com/user-attachments/assets/bb80404a-079d-44a1-b9bc-14eb2ab49a46 Inline deletion flow on a polymorphic "hasOne" relationship: https://github.com/user-attachments/assets/10f3587f-f70a-4cca-83ee-5dbcad32f063 - [x] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## Checklist: - [x] I have added tests that prove my fix is effective or that my feature works - [x] Existing test suite passes locally with my changes
508 lines
13 KiB
TypeScript
508 lines
13 KiB
TypeScript
import type { Payload } from 'payload'
|
|
|
|
import path from 'path'
|
|
import { getFileByPath } from 'payload'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
import { devUser } from '../credentials.js'
|
|
import { seedDB } from '../helpers/seed.js'
|
|
import { anotherArrayDoc, arrayDoc } from './collections/Array/shared.js'
|
|
import { blocksDoc } from './collections/Blocks/shared.js'
|
|
import { codeDoc } from './collections/Code/shared.js'
|
|
import { collapsibleDoc } from './collections/Collapsible/shared.js'
|
|
import { conditionalLogicDoc } from './collections/ConditionalLogic/shared.js'
|
|
import { dateDoc } from './collections/Date/shared.js'
|
|
import { anotherEmailDoc, emailDoc } from './collections/Email/shared.js'
|
|
import { groupDoc } from './collections/Group/shared.js'
|
|
import { jsonDoc } from './collections/JSON/shared.js'
|
|
import { lexicalDocData } from './collections/Lexical/data.js'
|
|
import { generateLexicalLocalizedRichText } from './collections/LexicalLocalized/generateLexicalRichText.js'
|
|
import { textToLexicalJSON } from './collections/LexicalLocalized/textToLexicalJSON.js'
|
|
import { lexicalMigrateDocData } from './collections/LexicalMigrate/data.js'
|
|
import { numberDoc } from './collections/Number/shared.js'
|
|
import { pointDoc } from './collections/Point/shared.js'
|
|
import { radiosDoc } from './collections/Radio/shared.js'
|
|
import { richTextBulletsDocData, richTextDocData } from './collections/RichText/data.js'
|
|
import { selectsDoc } from './collections/Select/shared.js'
|
|
import { tabsDoc } from './collections/Tabs/shared.js'
|
|
import { anotherTextDoc, textDoc } from './collections/Text/shared.js'
|
|
import { uploadsDoc } from './collections/Upload/shared.js'
|
|
import {
|
|
arrayFieldsSlug,
|
|
blockFieldsSlug,
|
|
codeFieldsSlug,
|
|
collapsibleFieldsSlug,
|
|
collectionSlugs,
|
|
conditionalLogicSlug,
|
|
dateFieldsSlug,
|
|
emailFieldsSlug,
|
|
groupFieldsSlug,
|
|
jsonFieldsSlug,
|
|
lexicalFieldsSlug,
|
|
lexicalLocalizedFieldsSlug,
|
|
lexicalMigrateFieldsSlug,
|
|
lexicalRelationshipFieldsSlug,
|
|
numberFieldsSlug,
|
|
pointFieldsSlug,
|
|
radioFieldsSlug,
|
|
relationshipFieldsSlug,
|
|
richTextFieldsSlug,
|
|
selectFieldsSlug,
|
|
tabsFieldsSlug,
|
|
textFieldsSlug,
|
|
uiSlug,
|
|
uploads2Slug,
|
|
uploadsMulti,
|
|
uploadsMultiPoly,
|
|
uploadsPoly,
|
|
uploadsSlug,
|
|
usersSlug,
|
|
} from './slugs.js'
|
|
|
|
const filename = fileURLToPath(import.meta.url)
|
|
const dirname = path.dirname(filename)
|
|
|
|
export const seed = async (_payload: Payload) => {
|
|
if (_payload.db.name === 'mongoose') {
|
|
await Promise.all(
|
|
_payload.config.collections.map(async (coll) => {
|
|
await new Promise((resolve, reject) => {
|
|
_payload.db?.collections[coll.slug]?.ensureIndexes(function (err) {
|
|
if (err) {
|
|
reject(err)
|
|
}
|
|
resolve(true)
|
|
})
|
|
})
|
|
}),
|
|
)
|
|
}
|
|
|
|
const jpgPath = path.resolve(dirname, './collections/Upload/payload.jpg')
|
|
const pngPath = path.resolve(dirname, './uploads/payload.png')
|
|
|
|
// Get both files in parallel
|
|
const [jpgFile, pngFile] = await Promise.all([getFileByPath(jpgPath), getFileByPath(pngPath)])
|
|
|
|
const createdArrayDoc = await _payload.create({
|
|
collection: arrayFieldsSlug,
|
|
data: arrayDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
const createdAnotherArrayDoc = await _payload.create({
|
|
collection: arrayFieldsSlug,
|
|
data: anotherArrayDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
const createdTextDoc = await _payload.create({
|
|
collection: textFieldsSlug,
|
|
data: textDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
const createdAnotherTextDoc = await _payload.create({
|
|
collection: textFieldsSlug,
|
|
data: anotherTextDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
const createdPNGDoc = await _payload.create({
|
|
collection: uploadsSlug,
|
|
data: {},
|
|
file: pngFile,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
const createdJPGDoc = await _payload.create({
|
|
collection: uploadsSlug,
|
|
data: {
|
|
...uploadsDoc,
|
|
media: createdPNGDoc.id,
|
|
},
|
|
file: jpgFile,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
// const createdJPGDocSlug2 = await _payload.create({
|
|
// collection: uploads2Slug,
|
|
// data: {
|
|
// ...uploadsDoc,
|
|
// },
|
|
// file: jpgFile,
|
|
// depth: 0,
|
|
// overrideAccess: true,
|
|
// })
|
|
|
|
// Create hasMany upload
|
|
await _payload.create({
|
|
collection: uploadsMulti,
|
|
data: {
|
|
media: [createdPNGDoc.id, createdJPGDoc.id],
|
|
},
|
|
})
|
|
|
|
// Create hasMany poly upload
|
|
// await _payload.create({
|
|
// collection: uploadsMultiPoly,
|
|
// data: {
|
|
// media: [
|
|
// { value: createdJPGDocSlug2.id, relationTo: uploads2Slug },
|
|
// { value: createdJPGDoc.id, relationTo: uploadsSlug },
|
|
// ],
|
|
// },
|
|
// })
|
|
|
|
// Create poly upload
|
|
await _payload.create({
|
|
collection: uploadsPoly,
|
|
data: {
|
|
media: { value: createdJPGDoc.id, relationTo: uploadsSlug },
|
|
},
|
|
})
|
|
// Create poly upload
|
|
// await _payload.create({
|
|
// collection: uploadsPoly,
|
|
// data: {
|
|
// media: { value: createdJPGDocSlug2.id, relationTo: uploads2Slug },
|
|
// },
|
|
// })
|
|
|
|
const formattedID =
|
|
_payload.db.defaultIDType === 'number' ? createdArrayDoc.id : `"${createdArrayDoc.id}"`
|
|
|
|
const formattedJPGID =
|
|
_payload.db.defaultIDType === 'number' ? createdJPGDoc.id : `"${createdJPGDoc.id}"`
|
|
|
|
const formattedTextID =
|
|
_payload.db.defaultIDType === 'number' ? createdTextDoc.id : `"${createdTextDoc.id}"`
|
|
|
|
const richTextDocWithRelId = JSON.parse(
|
|
JSON.stringify(richTextDocData)
|
|
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
|
|
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
|
|
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`),
|
|
)
|
|
const richTextBulletsDocWithRelId = JSON.parse(
|
|
JSON.stringify(richTextBulletsDocData)
|
|
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
|
|
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
|
|
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`),
|
|
)
|
|
|
|
const richTextDocWithRelationship = { ...richTextDocWithRelId }
|
|
|
|
const blocksDocWithRichText = {
|
|
...(blocksDoc as any),
|
|
}
|
|
|
|
blocksDocWithRichText.blocks[0].richText = richTextDocWithRelationship.richText
|
|
blocksDocWithRichText.localizedBlocks[0].richText = richTextDocWithRelationship.richText
|
|
|
|
await _payload.create({
|
|
collection: richTextFieldsSlug,
|
|
data: richTextBulletsDocWithRelId,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: emailFieldsSlug,
|
|
data: emailDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: emailFieldsSlug,
|
|
data: anotherEmailDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
const createdRichTextDoc = await _payload.create({
|
|
collection: richTextFieldsSlug,
|
|
data: richTextDocWithRelationship,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
const formattedRichTextDocID =
|
|
_payload.db.defaultIDType === 'number' ? createdRichTextDoc.id : `"${createdRichTextDoc.id}"`
|
|
|
|
const lexicalDocWithRelId = JSON.parse(
|
|
JSON.stringify(lexicalDocData)
|
|
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
|
|
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
|
|
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`)
|
|
.replace(/"\{\{RICH_TEXT_DOC_ID\}\}"/g, `${formattedRichTextDocID}`),
|
|
)
|
|
|
|
const lexicalMigrateDocWithRelId = JSON.parse(
|
|
JSON.stringify(lexicalMigrateDocData)
|
|
.replace(/"\{\{ARRAY_DOC_ID\}\}"/g, `${formattedID}`)
|
|
.replace(/"\{\{UPLOAD_DOC_ID\}\}"/g, `${formattedJPGID}`)
|
|
.replace(/"\{\{TEXT_DOC_ID\}\}"/g, `${formattedTextID}`)
|
|
.replace(/"\{\{RICH_TEXT_DOC_ID\}\}"/g, `${formattedRichTextDocID}`),
|
|
)
|
|
|
|
await _payload.create({
|
|
collection: usersSlug,
|
|
depth: 0,
|
|
data: {
|
|
email: devUser.email,
|
|
password: devUser.password,
|
|
},
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: collapsibleFieldsSlug,
|
|
data: collapsibleDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: conditionalLogicSlug,
|
|
data: conditionalLogicDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: groupFieldsSlug,
|
|
data: groupDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: selectFieldsSlug,
|
|
data: selectsDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: radioFieldsSlug,
|
|
data: radiosDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: tabsFieldsSlug,
|
|
data: tabsDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: pointFieldsSlug,
|
|
data: pointDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: dateFieldsSlug,
|
|
data: dateDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: codeFieldsSlug,
|
|
data: codeDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: jsonFieldsSlug,
|
|
data: jsonDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: blockFieldsSlug,
|
|
data: blocksDocWithRichText,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
const relationshipField1 = await _payload.create({
|
|
collection: relationshipFieldsSlug,
|
|
data: {
|
|
text: 'Relationship 1',
|
|
relationship: {
|
|
relationTo: textFieldsSlug,
|
|
value: createdTextDoc.id,
|
|
},
|
|
},
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
try {
|
|
await _payload.create({
|
|
collection: relationshipFieldsSlug,
|
|
data: {
|
|
text: 'Relationship 2',
|
|
relationToSelf: relationshipField1.id,
|
|
relationship: {
|
|
relationTo: textFieldsSlug,
|
|
value: createdAnotherTextDoc.id,
|
|
},
|
|
},
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
} catch (e) {
|
|
console.error(e)
|
|
}
|
|
|
|
await _payload.create({
|
|
collection: lexicalFieldsSlug,
|
|
data: lexicalDocWithRelId,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
const lexicalLocalizedDoc1 = await _payload.create({
|
|
collection: lexicalLocalizedFieldsSlug,
|
|
data: {
|
|
title: 'Localized Lexical en',
|
|
lexicalBlocksLocalized: textToLexicalJSON({ text: 'English text' }) as any,
|
|
lexicalBlocksSubLocalized: generateLexicalLocalizedRichText(
|
|
'Shared text',
|
|
'English text in block',
|
|
) as any,
|
|
},
|
|
locale: 'en',
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: lexicalRelationshipFieldsSlug,
|
|
data: {
|
|
richText: textToLexicalJSON({ text: 'English text' }) as any,
|
|
},
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.update({
|
|
collection: lexicalLocalizedFieldsSlug,
|
|
id: lexicalLocalizedDoc1.id,
|
|
data: {
|
|
title: 'Localized Lexical es',
|
|
lexicalBlocksLocalized: textToLexicalJSON({ text: 'Spanish text' }) as any,
|
|
lexicalBlocksSubLocalized: generateLexicalLocalizedRichText(
|
|
'Shared text',
|
|
'Spanish text in block',
|
|
(lexicalLocalizedDoc1.lexicalBlocksSubLocalized.root.children[1].fields as any).id,
|
|
) as any,
|
|
},
|
|
locale: 'es',
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
const lexicalLocalizedDoc2 = await _payload.create({
|
|
collection: lexicalLocalizedFieldsSlug,
|
|
data: {
|
|
title: 'Localized Lexical en 2',
|
|
lexicalSimple: textToLexicalJSON({
|
|
text: 'English text 2',
|
|
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
|
}),
|
|
lexicalBlocksLocalized: textToLexicalJSON({
|
|
text: 'English text 2',
|
|
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
|
}),
|
|
lexicalBlocksSubLocalized: textToLexicalJSON({
|
|
text: 'English text 2',
|
|
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
|
}),
|
|
},
|
|
locale: 'en',
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.update({
|
|
collection: lexicalLocalizedFieldsSlug,
|
|
id: lexicalLocalizedDoc2.id,
|
|
data: {
|
|
title: 'Localized Lexical es 2',
|
|
lexicalSimple: textToLexicalJSON({
|
|
text: 'Spanish text 2',
|
|
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
|
}),
|
|
lexicalBlocksLocalized: textToLexicalJSON({
|
|
text: 'Spanish text 2',
|
|
lexicalLocalizedRelID: lexicalLocalizedDoc1.id,
|
|
}),
|
|
},
|
|
locale: 'es',
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: lexicalMigrateFieldsSlug,
|
|
data: lexicalMigrateDocWithRelId,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: numberFieldsSlug,
|
|
data: { number: 2 },
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: numberFieldsSlug,
|
|
data: { number: 3 },
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: numberFieldsSlug,
|
|
data: numberDoc,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
|
|
await _payload.create({
|
|
collection: uiSlug,
|
|
data: {
|
|
text: 'text',
|
|
},
|
|
})
|
|
}
|
|
|
|
export async function clearAndSeedEverything(_payload: Payload) {
|
|
return await seedDB({
|
|
_payload,
|
|
collectionSlugs,
|
|
seedFunction: seed,
|
|
snapshotKey: 'fieldsTest',
|
|
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
|
})
|
|
}
|