Streamlined collection config
This commit is contained in:
		
							parent
							
								
									0a45ebb134
								
							
						
					
					
						commit
						65286f0c66
					
				| @ -1,9 +1,8 @@ | |||||||
| import { CollectionConfig } from "payload/types"; |  | ||||||
| import { slugField } from "../../fields/slugField/slugField"; | import { slugField } from "../../fields/slugField/slugField"; | ||||||
| import { CollectionGroup, KeysTypes } from "../../constants"; | import { CollectionGroup } from "../../constants"; | ||||||
| import { localizedFields } from "../../fields/translatedFields/translatedFields"; | import { localizedFields } from "../../fields/translatedFields/translatedFields"; | ||||||
| import { collectionSlug } from "../../utils/string"; |  | ||||||
| import { Contents } from "../Contents/Contents"; | import { Contents } from "../Contents/Contents"; | ||||||
|  | import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   slug: "slug", |   slug: "slug", | ||||||
| @ -13,53 +12,49 @@ const fields = { | |||||||
|   contents: "contents", |   contents: "contents", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { | export const ContentFolders = buildCollectionConfig( | ||||||
|   singular: "Content Folder", |   { | ||||||
|   plural: "Content Folders", |     singular: "Content Folder", | ||||||
| } as const satisfies { singular: string; plural: string }; |     plural: "Content Folders", | ||||||
| 
 |  | ||||||
| const slug = collectionSlug(labels.plural); |  | ||||||
| 
 |  | ||||||
| export const ContentFolders: CollectionConfig = { |  | ||||||
|   slug, |  | ||||||
|   labels, |  | ||||||
|   typescript: { interface: labels.singular }, |  | ||||||
|   defaultSort: fields.slug, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.slug, |  | ||||||
|     defaultColumns: [fields.slug, fields.translations], |  | ||||||
|     group: CollectionGroup.Collections, |  | ||||||
|   }, |   }, | ||||||
|   timestamps: false, |   ({ slug }) => ({ | ||||||
|   versions: false, |     defaultSort: fields.slug, | ||||||
|   fields: [ |     admin: { | ||||||
|     slugField({ name: fields.slug }), |       useAsTitle: fields.slug, | ||||||
|     localizedFields({ |       defaultColumns: [fields.slug, fields.translations], | ||||||
|       name: fields.translations, |       group: CollectionGroup.Collections, | ||||||
|       interfaceName: "ContentFoldersTranslation", |  | ||||||
|       admin: { |  | ||||||
|         useAsTitle: fields.name, |  | ||||||
|       }, |  | ||||||
|       fields: [{ name: fields.name, type: "text", required: true }], |  | ||||||
|     }), |  | ||||||
|     { |  | ||||||
|       type: "row", |  | ||||||
|       fields: [ |  | ||||||
|         { |  | ||||||
|           type: "relationship", |  | ||||||
|           name: fields.subfolders, |  | ||||||
|           relationTo: [slug], |  | ||||||
|           hasMany: true, |  | ||||||
|           admin: { width: "50%" }, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           type: "relationship", |  | ||||||
|           name: fields.contents, |  | ||||||
|           relationTo: [Contents.slug], |  | ||||||
|           hasMany: true, |  | ||||||
|           admin: { width: "50%" }, |  | ||||||
|         }, |  | ||||||
|       ], |  | ||||||
|     }, |     }, | ||||||
|   ], |     timestamps: false, | ||||||
| }; |     versions: false, | ||||||
|  |     fields: [ | ||||||
|  |       slugField({ name: fields.slug }), | ||||||
|  |       localizedFields({ | ||||||
|  |         name: fields.translations, | ||||||
|  |         interfaceName: "ContentFoldersTranslation", | ||||||
|  |         admin: { | ||||||
|  |           useAsTitle: fields.name, | ||||||
|  |         }, | ||||||
|  |         fields: [{ name: fields.name, type: "text", required: true }], | ||||||
|  |       }), | ||||||
|  |       { | ||||||
|  |         type: "row", | ||||||
|  |         fields: [ | ||||||
|  |           { | ||||||
|  |             type: "relationship", | ||||||
|  |             name: fields.subfolders, | ||||||
|  |             relationTo: [slug], | ||||||
|  |             hasMany: true, | ||||||
|  |             admin: { width: "50%" }, | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             type: "relationship", | ||||||
|  |             name: fields.contents, | ||||||
|  |             relationTo: [Contents.slug], | ||||||
|  |             hasMany: true, | ||||||
|  |             admin: { width: "50%" }, | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |   }) | ||||||
|  | ); | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import { CollectionConfig } from "payload/types"; |  | ||||||
| import { CollectionGroup } from "../../constants"; | import { CollectionGroup } from "../../constants"; | ||||||
| import { collectionSlug } from "../../utils/string"; | import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   filename: "filename", |   filename: "filename", | ||||||
| @ -8,45 +7,41 @@ const fields = { | |||||||
|   filesize: "filesize", |   filesize: "filesize", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { | export const ContentThumbnails = buildCollectionConfig( | ||||||
|   singular: "Content Thumbnail", |   { | ||||||
|   plural: "Content Thumbnails", |     singular: "Content Thumbnail", | ||||||
| } as const satisfies { singular: string; plural: string }; |     plural: "Content Thumbnails", | ||||||
| 
 |  | ||||||
| export const ContentThumbnails: CollectionConfig = { |  | ||||||
|   slug: collectionSlug(labels.plural), |  | ||||||
|   labels, |  | ||||||
|   typescript: { interface: labels.singular }, |  | ||||||
|   defaultSort: fields.filename, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.filename, |  | ||||||
|     group: CollectionGroup.Media, |  | ||||||
|   }, |   }, | ||||||
| 
 |   ({ labels }) => ({ | ||||||
|   upload: { |     defaultSort: fields.filename, | ||||||
|     staticDir: `../uploads/${labels.plural}`, |     admin: { | ||||||
|     mimeTypes: ["image/*"], |       useAsTitle: fields.filename, | ||||||
|     imageSizes: [ |       group: CollectionGroup.Media, | ||||||
|       { |     }, | ||||||
|         name: "og", |     upload: { | ||||||
|         height: 750, |       staticDir: `../uploads/${labels.plural}`, | ||||||
|         width: 1125, |       mimeTypes: ["image/*"], | ||||||
|         formatOptions: { |       imageSizes: [ | ||||||
|           format: "jpg", |         { | ||||||
|           options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, |           name: "og", | ||||||
|  |           height: 750, | ||||||
|  |           width: 1125, | ||||||
|  |           formatOptions: { | ||||||
|  |             format: "jpg", | ||||||
|  |             options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||||
|  |           }, | ||||||
|         }, |         }, | ||||||
|       }, |         { | ||||||
|       { |           name: "medium", | ||||||
|         name: "medium", |           height: 1000, | ||||||
|         height: 1000, |           width: 1500, | ||||||
|         width: 1500, |           formatOptions: { | ||||||
|         formatOptions: { |             format: "webp", | ||||||
|           format: "webp", |             options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||||
|           options: { effort: 6, quality: 80, alphaQuality: 80 }, |           }, | ||||||
|         }, |         }, | ||||||
|       }, |       ], | ||||||
|     ], |     }, | ||||||
|   }, |     fields: [], | ||||||
| 
 |   }) | ||||||
|   fields: [], | ); | ||||||
| }; |  | ||||||
|  | |||||||
| @ -1,5 +1,3 @@ | |||||||
| import { CollectionConfig } from "payload/types"; |  | ||||||
| import { collectionSlug } from "../../utils/string"; |  | ||||||
| import { CollectionGroup, FileTypes, KeysTypes } from "../../constants"; | import { CollectionGroup, FileTypes, KeysTypes } from "../../constants"; | ||||||
| import { slugField } from "../../fields/slugField/slugField"; | import { slugField } from "../../fields/slugField/slugField"; | ||||||
| import { imageField } from "../../fields/imageField/imageField"; | import { imageField } from "../../fields/imageField/imageField"; | ||||||
| @ -10,6 +8,7 @@ import { isDefined } from "../../utils/asserts"; | |||||||
| import { fileField } from "../../fields/fileField/fileField"; | import { fileField } from "../../fields/fileField/fileField"; | ||||||
| import { contentBlocks } from "./Blocks/blocks"; | import { contentBlocks } from "./Blocks/blocks"; | ||||||
| import { ContentThumbnails } from "../ContentThumbnails/ContentThumbnails"; | import { ContentThumbnails } from "../ContentThumbnails/ContentThumbnails"; | ||||||
|  | import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   slug: "slug", |   slug: "slug", | ||||||
| @ -31,187 +30,186 @@ const fields = { | |||||||
|   audio: "audio", |   audio: "audio", | ||||||
|   audioNotes: "videoNotes", |   audioNotes: "videoNotes", | ||||||
|   status: "status", |   status: "status", | ||||||
|  |   updatedBy: "updatedBy", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { | export const Contents = buildVersionedCollectionConfig( | ||||||
|   singular: "Content", |   { | ||||||
|   plural: "Contents", |     singular: "Content", | ||||||
| } as const satisfies { singular: string; plural: string }; |     plural: "Contents", | ||||||
| 
 |  | ||||||
| export const Contents: CollectionConfig = { |  | ||||||
|   slug: collectionSlug(labels.plural), |  | ||||||
|   labels, |  | ||||||
|   typescript: { interface: labels.singular }, |  | ||||||
|   defaultSort: fields.slug, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.slug, |  | ||||||
|     defaultColumns: [ |  | ||||||
|       fields.slug, |  | ||||||
|       fields.thumbnail, |  | ||||||
|       fields.categories, |  | ||||||
|       fields.type, |  | ||||||
|       fields.translations, |  | ||||||
|       fields.status, |  | ||||||
|     ], |  | ||||||
|     group: CollectionGroup.Collections, |  | ||||||
|     preview: (doc) => `https://accords-library.com/contents/${doc.slug}`, |  | ||||||
|   }, |   }, | ||||||
|   timestamps: true, |   () => ({ | ||||||
|   versions: { drafts: { autosave: true } }, |     defaultSort: fields.slug, | ||||||
|   fields: [ |     admin: { | ||||||
|     { |       useAsTitle: fields.slug, | ||||||
|       type: "row", |       description: | ||||||
|       fields: [ |         "All the contents (textual, audio, and video) from the Library or other online sources.", | ||||||
|         slugField({ name: fields.slug, admin: { width: "50%" } }), |       defaultColumns: [ | ||||||
|         imageField({ |         fields.slug, | ||||||
|           name: fields.thumbnail, |         fields.thumbnail, | ||||||
|           relationTo: ContentThumbnails.slug, |         fields.categories, | ||||||
|           admin: { width: "50%" }, |         fields.type, | ||||||
|         }), |         fields.translations, | ||||||
|  |         fields.status, | ||||||
|       ], |       ], | ||||||
|  |       group: CollectionGroup.Collections, | ||||||
|  |       preview: (doc) => `https://accords-library.com/contents/${doc.slug}`, | ||||||
|     }, |     }, | ||||||
|     { |     fields: [ | ||||||
|       type: "row", |       { | ||||||
|       fields: [ |         type: "row", | ||||||
|         { |         fields: [ | ||||||
|           name: fields.categories, |           slugField({ name: fields.slug, admin: { width: "50%" } }), | ||||||
|           type: "relationship", |           imageField({ | ||||||
|           relationTo: [Keys.slug], |             name: fields.thumbnail, | ||||||
|           filterOptions: { type: { equals: KeysTypes.Categories } }, |             relationTo: ContentThumbnails.slug, | ||||||
|           hasMany: true, |             admin: { width: "50%" }, | ||||||
|           admin: { allowCreate: false, width: "50%" }, |           }), | ||||||
|         }, |         ], | ||||||
|         { |       }, | ||||||
|           name: fields.type, |       { | ||||||
|           type: "relationship", |         type: "row", | ||||||
|           relationTo: [Keys.slug], |         fields: [ | ||||||
|           filterOptions: { type: { equals: KeysTypes.Contents } }, |           { | ||||||
|           admin: { allowCreate: false, width: "50%" }, |             name: fields.categories, | ||||||
|         }, |             type: "relationship", | ||||||
|       ], |             relationTo: [Keys.slug], | ||||||
|     }, |             filterOptions: { type: { equals: KeysTypes.Categories } }, | ||||||
|     localizedFields({ |             hasMany: true, | ||||||
|       name: fields.translations, |             admin: { allowCreate: false, width: "50%" }, | ||||||
|       admin: { useAsTitle: fields.title, hasSourceLanguage: true }, |  | ||||||
|       required: true, |  | ||||||
|       minRows: 1, |  | ||||||
|       fields: [ |  | ||||||
|         { |  | ||||||
|           type: "row", |  | ||||||
|           fields: [ |  | ||||||
|             { name: fields.pretitle, type: "text" }, |  | ||||||
|             { name: fields.title, type: "text", required: true }, |  | ||||||
|             { name: fields.subtitle, type: "text" }, |  | ||||||
|           ], |  | ||||||
|         }, |  | ||||||
|         { name: fields.summary, type: "textarea" }, |  | ||||||
|         { |  | ||||||
|           type: "tabs", |  | ||||||
|           admin: { |  | ||||||
|             condition: (_, siblingData) => |  | ||||||
|               isDefined(siblingData.language) && isDefined(siblingData.sourceLanguage), |  | ||||||
|           }, |           }, | ||||||
|           tabs: [ |           { | ||||||
|             { |             name: fields.type, | ||||||
|               label: "Text", |             type: "relationship", | ||||||
|               fields: [ |             relationTo: [Keys.slug], | ||||||
|                 { |             filterOptions: { type: { equals: KeysTypes.Contents } }, | ||||||
|                   type: "row", |             admin: { allowCreate: false, width: "50%" }, | ||||||
|                   fields: [ |           }, | ||||||
|                     { |         ], | ||||||
|                       name: fields.textTranscribers, |       }, | ||||||
|                       label: "Transcribers", |       localizedFields({ | ||||||
|                       type: "relationship", |         name: fields.translations, | ||||||
|                       relationTo: Recorders.slug, |         admin: { useAsTitle: fields.title, hasSourceLanguage: true }, | ||||||
|                       hasMany: true, |         required: true, | ||||||
|                       admin: { |         minRows: 1, | ||||||
|                         condition: (_, siblingData) => |         fields: [ | ||||||
|                           siblingData.language === siblingData.sourceLanguage, |           { | ||||||
|                         width: "50%", |             type: "row", | ||||||
|  |             fields: [ | ||||||
|  |               { name: fields.pretitle, type: "text" }, | ||||||
|  |               { name: fields.title, type: "text", required: true }, | ||||||
|  |               { name: fields.subtitle, type: "text" }, | ||||||
|  |             ], | ||||||
|  |           }, | ||||||
|  |           { name: fields.summary, type: "textarea" }, | ||||||
|  |           { | ||||||
|  |             type: "tabs", | ||||||
|  |             admin: { | ||||||
|  |               condition: (_, siblingData) => | ||||||
|  |                 isDefined(siblingData.language) && isDefined(siblingData.sourceLanguage), | ||||||
|  |             }, | ||||||
|  |             tabs: [ | ||||||
|  |               { | ||||||
|  |                 label: "Text", | ||||||
|  |                 fields: [ | ||||||
|  |                   { | ||||||
|  |                     type: "row", | ||||||
|  |                     fields: [ | ||||||
|  |                       { | ||||||
|  |                         name: fields.textTranscribers, | ||||||
|  |                         label: "Transcribers", | ||||||
|  |                         type: "relationship", | ||||||
|  |                         relationTo: Recorders.slug, | ||||||
|  |                         hasMany: true, | ||||||
|  |                         admin: { | ||||||
|  |                           condition: (_, siblingData) => | ||||||
|  |                             siblingData.language === siblingData.sourceLanguage, | ||||||
|  |                           width: "50%", | ||||||
|  |                         }, | ||||||
|                       }, |                       }, | ||||||
|                     }, |                       { | ||||||
|                     { |                         name: fields.textTranslators, | ||||||
|                       name: fields.textTranslators, |                         label: "Translators", | ||||||
|                       label: "Translators", |                         type: "relationship", | ||||||
|                       type: "relationship", |                         relationTo: Recorders.slug, | ||||||
|                       relationTo: Recorders.slug, |                         hasMany: true, | ||||||
|                       hasMany: true, |                         admin: { | ||||||
|                       admin: { |                           condition: (_, siblingData) => | ||||||
|                         condition: (_, siblingData) => |                             siblingData.language !== siblingData.sourceLanguage, | ||||||
|                           siblingData.language !== siblingData.sourceLanguage, |                           width: "50%", | ||||||
|                         width: "50%", |                         }, | ||||||
|                       }, |                       }, | ||||||
|                     }, |                       { | ||||||
|                     { |                         name: fields.textProofreaders, | ||||||
|                       name: fields.textProofreaders, |                         label: "Proofreaders", | ||||||
|                       label: "Proofreaders", |                         type: "relationship", | ||||||
|                       type: "relationship", |                         relationTo: Recorders.slug, | ||||||
|                       relationTo: Recorders.slug, |                         hasMany: true, | ||||||
|                       hasMany: true, |                         admin: { width: "50%" }, | ||||||
|                       admin: { width: "50%" }, |                       }, | ||||||
|                     }, |                     ], | ||||||
|                   ], |                   }, | ||||||
|                 }, |                   { | ||||||
|                 { |                     name: fields.textContent, | ||||||
|                   name: fields.textContent, |                     label: "Content", | ||||||
|                   label: "Content", |                     labels: { singular: "Block", plural: "Blocks" }, | ||||||
|                   labels: { singular: "Block", plural: "Blocks" }, |                     type: "blocks", | ||||||
|                   type: "blocks", |                     admin: { initCollapsed: true }, | ||||||
|                   admin: { initCollapsed: true }, |                     blocks: contentBlocks, | ||||||
|                   blocks: contentBlocks, |                   }, | ||||||
|                 }, |                   { | ||||||
|                 { |                     name: fields.textNotes, | ||||||
|                   name: fields.textNotes, |                     label: "Notes", | ||||||
|                   label: "Notes", |                     type: "textarea", | ||||||
|                   type: "textarea", |                   }, | ||||||
|                 }, |                 ], | ||||||
|               ], |               }, | ||||||
|             }, |               { | ||||||
|             { |                 label: "Video", | ||||||
|               label: "Video", |                 fields: [ | ||||||
|               fields: [ |                   { | ||||||
|                 { |                     type: "row", | ||||||
|                   type: "row", |                     fields: [ | ||||||
|                   fields: [ |                       fileField({ | ||||||
|                     fileField({ |                         name: fields.video, | ||||||
|                       name: fields.video, |                         filterOptions: { type: { equals: FileTypes.ContentVideo } }, | ||||||
|                       filterOptions: { type: { equals: FileTypes.ContentVideo } }, |                         admin: { width: "50%" }, | ||||||
|                       admin: { width: "50%" }, |                       }), | ||||||
|                     }), |                       { | ||||||
|                     { |                         name: fields.videoNotes, | ||||||
|                       name: fields.videoNotes, |                         label: "Notes", | ||||||
|                       label: "Notes", |                         type: "textarea", | ||||||
|                       type: "textarea", |                         admin: { width: "50%" }, | ||||||
|                       admin: { width: "50%" }, |                       }, | ||||||
|                     }, |                     ], | ||||||
|                   ], |                   }, | ||||||
|                 }, |                 ], | ||||||
|               ], |               }, | ||||||
|             }, |               { | ||||||
|             { |                 label: "Audio", | ||||||
|               label: "Audio", |                 fields: [ | ||||||
|               fields: [ |                   { | ||||||
|                 { |                     type: "row", | ||||||
|                   type: "row", |                     fields: [ | ||||||
|                   fields: [ |                       fileField({ | ||||||
|                     fileField({ |                         name: fields.audio, | ||||||
|                       name: fields.audio, |                         filterOptions: { type: { equals: FileTypes.ContentAudio } }, | ||||||
|                       filterOptions: { type: { equals: FileTypes.ContentAudio } }, |                         admin: { width: "50%" }, | ||||||
|                       admin: { width: "50%" }, |                       }), | ||||||
|                     }), |                       { | ||||||
|                     { |                         name: fields.audioNotes, | ||||||
|                       name: fields.audioNotes, |                         label: "Notes", | ||||||
|                       label: "Notes", |                         type: "textarea", | ||||||
|                       type: "textarea", |                         admin: { width: "50%" }, | ||||||
|                       admin: { width: "50%" }, |                       }, | ||||||
|                     }, |                     ], | ||||||
|                   ], |                   }, | ||||||
|                 }, |                 ], | ||||||
|               ], |               }, | ||||||
|             }, |             ], | ||||||
|           ], |           }, | ||||||
|         }, |         ], | ||||||
|       ], |       }), | ||||||
|     }), |     ], | ||||||
|   ], |   }) | ||||||
| }; | ); | ||||||
|  | |||||||
| @ -1,38 +1,34 @@ | |||||||
| import { CollectionConfig } from "payload/types"; |  | ||||||
| import { CollectionGroup, FileTypes } from "../../constants"; | import { CollectionGroup, FileTypes } from "../../constants"; | ||||||
| import { collectionSlug } from "../../utils/string"; | import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   filename: "filename", |   filename: "filename", | ||||||
|   type: "type", |   type: "type", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { | export const Files = buildCollectionConfig( | ||||||
|   singular: "File", |   { | ||||||
|   plural: "Files", |     singular: "File", | ||||||
| } as const satisfies { singular: string; plural: string }; |     plural: "Files", | ||||||
| 
 |  | ||||||
| export const Files: CollectionConfig = { |  | ||||||
|   slug: collectionSlug(labels.plural), |  | ||||||
|   labels, |  | ||||||
|   typescript: { interface: labels.singular }, |  | ||||||
|   defaultSort: fields.filename, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.filename, |  | ||||||
|     group: CollectionGroup.Media, |  | ||||||
|   }, |   }, | ||||||
| 
 |   () => ({ | ||||||
|   fields: [ |     defaultSort: fields.filename, | ||||||
|     { |     admin: { | ||||||
|       name: fields.filename, |       useAsTitle: fields.filename, | ||||||
|       required: true, |       group: CollectionGroup.Media, | ||||||
|       type: "text", |  | ||||||
|     }, |     }, | ||||||
|     { |     fields: [ | ||||||
|       name: fields.type, |       { | ||||||
|       type: "select", |         name: fields.filename, | ||||||
|       required: true, |         required: true, | ||||||
|       options: Object.entries(FileTypes).map(([value, label]) => ({ label, value })), |         type: "text", | ||||||
|     }, |       }, | ||||||
|   ], |       { | ||||||
| }; |         name: fields.type, | ||||||
|  |         type: "select", | ||||||
|  |         required: true, | ||||||
|  |         options: Object.entries(FileTypes).map(([value, label]) => ({ label, value })), | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |   }) | ||||||
|  | ); | ||||||
|  | |||||||
| @ -2,9 +2,9 @@ import { CollectionConfig } from "payload/types"; | |||||||
| import { slugField } from "../../fields/slugField/slugField"; | import { slugField } from "../../fields/slugField/slugField"; | ||||||
| import { CollectionGroup, KeysTypes } from "../../constants"; | import { CollectionGroup, KeysTypes } from "../../constants"; | ||||||
| import { localizedFields } from "../../fields/translatedFields/translatedFields"; | import { localizedFields } from "../../fields/translatedFields/translatedFields"; | ||||||
| import { collectionSlug } from "../../utils/string"; |  | ||||||
| import { Key } from "../../types/collections"; | import { Key } from "../../types/collections"; | ||||||
| import { isDefined } from "../../utils/asserts"; | import { isDefined } from "../../utils/asserts"; | ||||||
|  | import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   slug: "slug", |   slug: "slug", | ||||||
| @ -14,56 +14,54 @@ const fields = { | |||||||
|   short: "short", |   short: "short", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { |  | ||||||
|   singular: "Key", |  | ||||||
|   plural: "Keys", |  | ||||||
| } as const satisfies { singular: string; plural: string }; |  | ||||||
| 
 |  | ||||||
| const keysTypesWithShort: (keyof typeof KeysTypes)[] = ["Categories", "GamePlatforms"]; | const keysTypesWithShort: (keyof typeof KeysTypes)[] = ["Categories", "GamePlatforms"]; | ||||||
| 
 | 
 | ||||||
| export const Keys: CollectionConfig = { | export const Keys: CollectionConfig = buildCollectionConfig( | ||||||
|   slug: collectionSlug(labels.plural), |   { | ||||||
|   labels, |     singular: "Key", | ||||||
|   typescript: { interface: labels.singular }, |     plural: "Keys", | ||||||
|   defaultSort: fields.slug, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.slug, |  | ||||||
|     defaultColumns: [fields.slug, fields.type, fields.translations], |  | ||||||
|     group: CollectionGroup.Meta, |  | ||||||
|   }, |   }, | ||||||
|   timestamps: false, |   () => ({ | ||||||
|   versions: false, |     defaultSort: fields.slug, | ||||||
|   fields: [ |     admin: { | ||||||
|     slugField({ name: fields.slug }), |       useAsTitle: fields.slug, | ||||||
|     { |       defaultColumns: [fields.slug, fields.type, fields.translations], | ||||||
|       name: fields.type, |       group: CollectionGroup.Meta, | ||||||
|       type: "select", |  | ||||||
|       required: true, |  | ||||||
|       options: Object.entries(KeysTypes).map(([value, label]) => ({ label, value })), |  | ||||||
|     }, |     }, | ||||||
|     localizedFields({ |     timestamps: false, | ||||||
|       name: fields.translations, |     versions: false, | ||||||
|       interfaceName: "CategoryTranslations", |     fields: [ | ||||||
|       admin: { |       slugField({ name: fields.slug }), | ||||||
|         useAsTitle: fields.name, |       { | ||||||
|  |         name: fields.type, | ||||||
|  |         type: "select", | ||||||
|  |         required: true, | ||||||
|  |         options: Object.entries(KeysTypes).map(([value, label]) => ({ label, value })), | ||||||
|       }, |       }, | ||||||
|       fields: [ |       localizedFields({ | ||||||
|         { |         name: fields.translations, | ||||||
|           type: "row", |         interfaceName: "CategoryTranslations", | ||||||
|           fields: [ |         admin: { | ||||||
|             { name: fields.name, type: "text", required: true, admin: { width: "50%" } }, |           useAsTitle: fields.name, | ||||||
|             { |  | ||||||
|               name: fields.short, |  | ||||||
|               type: "text", |  | ||||||
|               admin: { |  | ||||||
|                 condition: (data: Partial<Key>) => |  | ||||||
|                   isDefined(data.type) && keysTypesWithShort.includes(data.type), |  | ||||||
|                 width: "50%", |  | ||||||
|               }, |  | ||||||
|             }, |  | ||||||
|           ], |  | ||||||
|         }, |         }, | ||||||
|       ], |         fields: [ | ||||||
|     }), |           { | ||||||
|   ], |             type: "row", | ||||||
| }; |             fields: [ | ||||||
|  |               { name: fields.name, type: "text", required: true, admin: { width: "50%" } }, | ||||||
|  |               { | ||||||
|  |                 name: fields.short, | ||||||
|  |                 type: "text", | ||||||
|  |                 admin: { | ||||||
|  |                   condition: (data: Partial<Key>) => | ||||||
|  |                     isDefined(data.type) && keysTypesWithShort.includes(data.type), | ||||||
|  |                   width: "50%", | ||||||
|  |                 }, | ||||||
|  |               }, | ||||||
|  |             ], | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }), | ||||||
|  |     ], | ||||||
|  |   }) | ||||||
|  | ); | ||||||
|  | |||||||
| @ -1,46 +1,43 @@ | |||||||
| import { CollectionConfig } from "payload/types"; |  | ||||||
| import { CollectionGroup } from "../constants"; | import { CollectionGroup } from "../constants"; | ||||||
| import { collectionSlug } from "../utils/string"; | import { buildCollectionConfig } from "../utils/collectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   id: "id", |   id: "id", | ||||||
|   name: "name", |   name: "name", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { | export const Languages = buildCollectionConfig( | ||||||
|   singular: "Language", |   { | ||||||
|   plural: "Languages", |     singular: "Language", | ||||||
| } as const satisfies { singular: string; plural: string }; |     plural: "Languages", | ||||||
| 
 |  | ||||||
| export const Languages: CollectionConfig = { |  | ||||||
|   slug: collectionSlug(labels.plural), |  | ||||||
|   labels, |  | ||||||
|   typescript: { interface: labels.singular }, |  | ||||||
|   defaultSort: fields.name, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.name, |  | ||||||
|     defaultColumns: [fields.name, fields.id], |  | ||||||
|     group: CollectionGroup.Meta, |  | ||||||
|   }, |   }, | ||||||
|   timestamps: false, |   () => ({ | ||||||
|   fields: [ |     defaultSort: fields.name, | ||||||
|     { |     admin: { | ||||||
|       name: fields.id, |       useAsTitle: fields.name, | ||||||
|       type: "text", |       defaultColumns: [fields.name, fields.id], | ||||||
|       unique: true, |       group: CollectionGroup.Meta, | ||||||
|       required: true, |     }, | ||||||
|       validate: (value) => { |     timestamps: false, | ||||||
|         if (/^[a-z]{2}(-[a-z]{2})?$/g.test(value)) { |     fields: [ | ||||||
|           return true; |       { | ||||||
|         } |         name: fields.id, | ||||||
|         return "The code must be a valid IETF language tag and lowercase (i.e: en, pt-pt, fr, zh-tw...)"; |         type: "text", | ||||||
|  |         unique: true, | ||||||
|  |         required: true, | ||||||
|  |         validate: (value) => { | ||||||
|  |           if (/^[a-z]{2}(-[a-z]{2})?$/g.test(value)) { | ||||||
|  |             return true; | ||||||
|  |           } | ||||||
|  |           return "The code must be a valid IETF language tag and lowercase (i.e: en, pt-pt, fr, zh-tw...)"; | ||||||
|  |         }, | ||||||
|       }, |       }, | ||||||
|     }, |       { | ||||||
|     { |         name: fields.name, | ||||||
|       name: fields.name, |         type: "text", | ||||||
|       type: "text", |         unique: true, | ||||||
|       unique: true, |         required: true, | ||||||
|       required: true, |       }, | ||||||
|     }, |     ], | ||||||
|   ], |   }) | ||||||
| }; | ); | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import { CollectionConfig } from "payload/types"; |  | ||||||
| import { CollectionGroup } from "../../constants"; | import { CollectionGroup } from "../../constants"; | ||||||
| import { collectionSlug } from "../../utils/string"; | import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   filename: "filename", |   filename: "filename", | ||||||
| @ -8,57 +7,53 @@ const fields = { | |||||||
|   filesize: "filesize", |   filesize: "filesize", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { | export const LibraryItemThumbnails = buildCollectionConfig( | ||||||
|   singular: "Library Item Thumbnail", |   { | ||||||
|   plural: "Library Item Thumbnails", |     singular: "Library Item Thumbnail", | ||||||
| } as const satisfies { singular: string; plural: string }; |     plural: "Library Item Thumbnails", | ||||||
| 
 |  | ||||||
| export const LibraryItemThumbnails: CollectionConfig = { |  | ||||||
|   slug: collectionSlug(labels.plural), |  | ||||||
|   labels, |  | ||||||
|   typescript: { interface: labels.singular }, |  | ||||||
|   defaultSort: fields.filename, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.filename, |  | ||||||
|     group: CollectionGroup.Media, |  | ||||||
|   }, |   }, | ||||||
| 
 |   ({ labels }) => ({ | ||||||
|   upload: { |     defaultSort: fields.filename, | ||||||
|     staticDir: `../uploads/${labels.plural}`, |     admin: { | ||||||
|     mimeTypes: ["image/*"], |       useAsTitle: fields.filename, | ||||||
|     imageSizes: [ |       group: CollectionGroup.Media, | ||||||
|       { |     }, | ||||||
|         name: "og", |     upload: { | ||||||
|         height: 1024, |       staticDir: `../uploads/${labels.plural}`, | ||||||
|         width: 1024, |       mimeTypes: ["image/*"], | ||||||
|         fit: "contain", |       imageSizes: [ | ||||||
|         formatOptions: { |         { | ||||||
|           format: "jpg", |           name: "og", | ||||||
|           options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, |           height: 1024, | ||||||
|  |           width: 1024, | ||||||
|  |           fit: "contain", | ||||||
|  |           formatOptions: { | ||||||
|  |             format: "jpg", | ||||||
|  |             options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||||
|  |           }, | ||||||
|         }, |         }, | ||||||
|       }, |         { | ||||||
|       { |           name: "medium", | ||||||
|         name: "medium", |           height: 1024, | ||||||
|         height: 1024, |           width: 1024, | ||||||
|         width: 1024, |           fit: "contain", | ||||||
|         fit: "contain", |           formatOptions: { | ||||||
|         formatOptions: { |             format: "webp", | ||||||
|           format: "webp", |             options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||||
|           options: { effort: 6, quality: 80, alphaQuality: 80 }, |           }, | ||||||
|         }, |         }, | ||||||
|       }, |         { | ||||||
|       { |           name: "large", | ||||||
|         name: "large", |           height: 2048, | ||||||
|         height: 2048, |           width: 2048, | ||||||
|         width: 2048, |           fit: "contain", | ||||||
|         fit: "contain", |           formatOptions: { | ||||||
|         formatOptions: { |             format: "webp", | ||||||
|           format: "webp", |             options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||||
|           options: { effort: 6, quality: 80, alphaQuality: 80 }, |           }, | ||||||
|         }, |         }, | ||||||
|       }, |       ], | ||||||
|     ], |     }, | ||||||
|   }, |     fields: [], | ||||||
| 
 |   }) | ||||||
|   fields: [], | ); | ||||||
| }; |  | ||||||
|  | |||||||
| @ -1,4 +1,3 @@ | |||||||
| import { CollectionConfig } from "payload/types"; |  | ||||||
| import { | import { | ||||||
|   CollectionGroup, |   CollectionGroup, | ||||||
|   KeysTypes, |   KeysTypes, | ||||||
| @ -8,12 +7,12 @@ import { | |||||||
| } from "../../constants"; | } from "../../constants"; | ||||||
| import { slugField } from "../../fields/slugField/slugField"; | import { slugField } from "../../fields/slugField/slugField"; | ||||||
| import { imageField } from "../../fields/imageField/imageField"; | import { imageField } from "../../fields/imageField/imageField"; | ||||||
| import { collectionSlug } from "../../utils/string"; |  | ||||||
| import { isDefined, isUndefined } from "../../utils/asserts"; | import { isDefined, isUndefined } from "../../utils/asserts"; | ||||||
| import { LibraryItemThumbnails } from "../LibraryItemThumbnails/LibraryItemThumbnails"; | import { LibraryItemThumbnails } from "../LibraryItemThumbnails/LibraryItemThumbnails"; | ||||||
| import { LibraryItem } from "../../types/collections"; | import { LibraryItem } from "../../types/collections"; | ||||||
| import { Keys } from "../Keys/Keys"; | import { Keys } from "../Keys/Keys"; | ||||||
| import { Languages } from "../Languages"; | import { Languages } from "../Languages"; | ||||||
|  | import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   status: "status", |   status: "status", | ||||||
| @ -42,11 +41,6 @@ const fields = { | |||||||
|   audioSubtype: "audioSubtype", |   audioSubtype: "audioSubtype", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { |  | ||||||
|   singular: "Library Item", |  | ||||||
|   plural: "Library Items", |  | ||||||
| } as const satisfies { singular: string; plural: string }; |  | ||||||
| 
 |  | ||||||
| const validateSizeValue = (value?: number) => { | const validateSizeValue = (value?: number) => { | ||||||
|   if (isDefined(value) && value <= 0) return "This value must be greater than 0"; |   if (isDefined(value) && value <= 0) return "This value must be greater than 0"; | ||||||
|   return true; |   return true; | ||||||
| @ -58,216 +52,220 @@ const validateRequiredSizeValue = (value?: number) => { | |||||||
|   return true; |   return true; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const LibraryItems: CollectionConfig = { | export const LibraryItems = buildVersionedCollectionConfig( | ||||||
|   slug: collectionSlug(labels.plural), |   { | ||||||
|   labels, |     singular: "Library Item", | ||||||
|   typescript: { interface: labels.singular }, |     plural: "Library Items", | ||||||
|   defaultSort: fields.slug, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.slug, |  | ||||||
|     defaultColumns: [fields.slug, fields.thumbnail, fields.status], |  | ||||||
|     group: CollectionGroup.Collections, |  | ||||||
|     preview: (doc) => `https://accords-library.com/library/${doc.slug}`, |  | ||||||
|   }, |   }, | ||||||
|   timestamps: true, |   () => ({ | ||||||
|   versions: { drafts: { autosave: true } }, |     defaultSort: fields.slug, | ||||||
|   fields: [ |     admin: { | ||||||
|     { |       useAsTitle: fields.slug, | ||||||
|       type: "row", |       description: | ||||||
|       fields: [ |         "A comprehensive list of all Yokoverse’s side materials (books, novellas, artbooks, \ | ||||||
|         slugField({ name: fields.slug, admin: { width: "50%" } }), | stage plays, manga, drama CDs, and comics).", | ||||||
|         imageField({ |       defaultColumns: [fields.slug, fields.thumbnail, fields.status], | ||||||
|           name: fields.thumbnail, |       group: CollectionGroup.Collections, | ||||||
|           relationTo: LibraryItemThumbnails.slug, |       preview: (doc) => `https://accords-library.com/library/${doc.slug}`, | ||||||
|           admin: { width: "50%" }, |  | ||||||
|         }), |  | ||||||
|       ], |  | ||||||
|     }, |     }, | ||||||
|     { |     fields: [ | ||||||
|       type: "row", |       { | ||||||
|       fields: [ |         type: "row", | ||||||
|         { name: fields.pretitle, type: "text" }, |         fields: [ | ||||||
|         { name: fields.title, type: "text", required: true }, |           slugField({ name: fields.slug, admin: { width: "50%" } }), | ||||||
|         { name: fields.subtitle, type: "text" }, |           imageField({ | ||||||
|       ], |             name: fields.thumbnail, | ||||||
|     }, |             relationTo: LibraryItemThumbnails.slug, | ||||||
|     { |             admin: { width: "50%" }, | ||||||
|       type: "row", |           }), | ||||||
|       fields: [ |         ], | ||||||
|         { |  | ||||||
|           name: fields.rootItem, |  | ||||||
|           type: "checkbox", |  | ||||||
|           required: true, |  | ||||||
|           defaultValue: true, |  | ||||||
|           admin: { |  | ||||||
|             description: "Only items that can be sold separetely should be root items.", |  | ||||||
|             width: "25%", |  | ||||||
|           }, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           name: fields.primary, |  | ||||||
|           type: "checkbox", |  | ||||||
|           required: true, |  | ||||||
|           defaultValue: true, |  | ||||||
|           admin: { |  | ||||||
|             description: |  | ||||||
|               "A primary item is an official item that focuses primarly on one or more of our Categories.", |  | ||||||
|             width: "25%", |  | ||||||
|           }, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           name: fields.digital, |  | ||||||
|           type: "checkbox", |  | ||||||
|           required: true, |  | ||||||
|           defaultValue: false, |  | ||||||
|           admin: { |  | ||||||
|             description: |  | ||||||
|               "The item is the digital version of another item, or the item is sold only digitally.", |  | ||||||
|             width: "25%", |  | ||||||
|           }, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           name: fields.downloadable, |  | ||||||
|           type: "checkbox", |  | ||||||
|           required: true, |  | ||||||
|           defaultValue: false, |  | ||||||
|           admin: { |  | ||||||
|             description: "Are the scans available for download?", |  | ||||||
|             width: "25%", |  | ||||||
|           }, |  | ||||||
|         }, |  | ||||||
|       ], |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       name: "size", |  | ||||||
|       type: "group", |  | ||||||
|       admin: { condition: (data) => !data.digital }, |  | ||||||
|       fields: [ |  | ||||||
|         { |  | ||||||
|           type: "row", |  | ||||||
|           fields: [ |  | ||||||
|             { |  | ||||||
|               name: fields.width, |  | ||||||
|               type: "number", |  | ||||||
|               validate: validateRequiredSizeValue, |  | ||||||
|               admin: { step: 1, width: "33%", description: "in mm." }, |  | ||||||
|             }, |  | ||||||
|             { |  | ||||||
|               name: fields.height, |  | ||||||
|               type: "number", |  | ||||||
|               validate: validateRequiredSizeValue, |  | ||||||
|               admin: { step: 1, width: "33%", description: "in mm." }, |  | ||||||
|             }, |  | ||||||
|             { |  | ||||||
|               name: fields.thickness, |  | ||||||
|               type: "number", |  | ||||||
|               validate: validateSizeValue, |  | ||||||
|               admin: { step: 1, width: "33%", description: "in mm." }, |  | ||||||
|             }, |  | ||||||
|           ], |  | ||||||
|         }, |  | ||||||
|       ], |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       name: fields.itemType, |  | ||||||
|       type: "radio", |  | ||||||
|       options: Object.entries(LibraryItemsTypes).map(([value, label]) => ({ label, value })), |  | ||||||
|       admin: { |  | ||||||
|         layout: "horizontal", |  | ||||||
|       }, |       }, | ||||||
|     }, |       { | ||||||
|     { |         type: "row", | ||||||
|       name: fields.textual, |         fields: [ | ||||||
|       type: "group", |           { name: fields.pretitle, type: "text" }, | ||||||
|       admin: { |           { name: fields.title, type: "text", required: true }, | ||||||
|         condition: (data: Partial<LibraryItem>) => data.itemType === LibraryItemsTypes.Textual, |           { name: fields.subtitle, type: "text" }, | ||||||
|  |         ], | ||||||
|       }, |       }, | ||||||
|       fields: [ |       { | ||||||
|         { |         type: "row", | ||||||
|           type: "row", |         fields: [ | ||||||
|           fields: [ |           { | ||||||
|             { |             name: fields.rootItem, | ||||||
|               name: fields.textualSubtype, |             type: "checkbox", | ||||||
|               label: "Subtype", |             required: true, | ||||||
|               type: "relationship", |             defaultValue: true, | ||||||
|               relationTo: [Keys.slug], |             admin: { | ||||||
|               filterOptions: { type: { equals: KeysTypes.LibraryTextual } }, |               description: "Only items that can be sold separetely should be root items.", | ||||||
|               hasMany: true, |               width: "25%", | ||||||
|               admin: { allowCreate: false, width: "50%" }, |  | ||||||
|             }, |             }, | ||||||
|             { |           }, | ||||||
|               name: fields.textualLanguages, |           { | ||||||
|               type: "relationship", |             name: fields.primary, | ||||||
|               relationTo: [Languages.slug], |             type: "checkbox", | ||||||
|               hasMany: true, |             required: true, | ||||||
|               admin: { allowCreate: false, width: "50%" }, |             defaultValue: true, | ||||||
|  |             admin: { | ||||||
|  |               description: | ||||||
|  |                 "A primary item is an official item that focuses primarly on one or more of our Categories.", | ||||||
|  |               width: "25%", | ||||||
|             }, |             }, | ||||||
|           ], |           }, | ||||||
|         }, |           { | ||||||
|         { |             name: fields.digital, | ||||||
|           type: "row", |             type: "checkbox", | ||||||
|           fields: [ |             required: true, | ||||||
|             { name: fields.textualPageCount, type: "number", min: 1, admin: { width: "33%" } }, |             defaultValue: false, | ||||||
|             { |             admin: { | ||||||
|               name: fields.textualBindingType, |               description: | ||||||
|               label: "Binding Type", |                 "The item is the digital version of another item, or the item is sold only digitally.", | ||||||
|               type: "radio", |               width: "25%", | ||||||
|               options: Object.entries(LibraryItemsTextualBindingTypes).map(([value, label]) => ({ |             }, | ||||||
|                 label, |           }, | ||||||
|                 value, |           { | ||||||
|               })), |             name: fields.downloadable, | ||||||
|               admin: { |             type: "checkbox", | ||||||
|                 layout: "horizontal", |             required: true, | ||||||
|                 width: "33%", |             defaultValue: false, | ||||||
|  |             admin: { | ||||||
|  |               description: "Are the scans available for download?", | ||||||
|  |               width: "25%", | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: "size", | ||||||
|  |         type: "group", | ||||||
|  |         admin: { condition: (data) => !data.digital }, | ||||||
|  |         fields: [ | ||||||
|  |           { | ||||||
|  |             type: "row", | ||||||
|  |             fields: [ | ||||||
|  |               { | ||||||
|  |                 name: fields.width, | ||||||
|  |                 type: "number", | ||||||
|  |                 validate: validateRequiredSizeValue, | ||||||
|  |                 admin: { step: 1, width: "33%", description: "in mm." }, | ||||||
|               }, |               }, | ||||||
|             }, |               { | ||||||
|             { |                 name: fields.height, | ||||||
|               name: fields.textualPageOrder, |                 type: "number", | ||||||
|               label: "Page Order", |                 validate: validateRequiredSizeValue, | ||||||
|               type: "radio", |                 admin: { step: 1, width: "33%", description: "in mm." }, | ||||||
|               options: Object.entries(LibraryItemsTextualPageOrders).map(([value, label]) => ({ |  | ||||||
|                 label, |  | ||||||
|                 value, |  | ||||||
|               })), |  | ||||||
|               admin: { |  | ||||||
|                 layout: "horizontal", |  | ||||||
|                 width: "33%", |  | ||||||
|               }, |               }, | ||||||
|             }, |               { | ||||||
|           ], |                 name: fields.thickness, | ||||||
|         }, |                 type: "number", | ||||||
|       ], |                 validate: validateSizeValue, | ||||||
|     }, |                 admin: { step: 1, width: "33%", description: "in mm." }, | ||||||
|     { |               }, | ||||||
|       name: fields.audio, |             ], | ||||||
|       type: "group", |           }, | ||||||
|       admin: { |         ], | ||||||
|         condition: (data: Partial<LibraryItem>) => data.itemType === LibraryItemsTypes.Audio, |  | ||||||
|       }, |       }, | ||||||
|       fields: [ |       { | ||||||
|         { |         name: fields.itemType, | ||||||
|           type: "row", |         type: "radio", | ||||||
|           fields: [ |         options: Object.entries(LibraryItemsTypes).map(([value, label]) => ({ label, value })), | ||||||
|             { |         admin: { | ||||||
|               name: fields.audioSubtype, |           layout: "horizontal", | ||||||
|               label: "Subtype", |  | ||||||
|               type: "relationship", |  | ||||||
|               relationTo: [Keys.slug], |  | ||||||
|               filterOptions: { type: { equals: KeysTypes.LibraryAudio } }, |  | ||||||
|               hasMany: true, |  | ||||||
|               admin: { allowCreate: false, width: "50%" }, |  | ||||||
|             }, |  | ||||||
|           ], |  | ||||||
|         }, |         }, | ||||||
|       ], |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       name: fields.releaseDate, |  | ||||||
|       type: "date", |  | ||||||
|       admin: { |  | ||||||
|         date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" }, |  | ||||||
|         position: "sidebar", |  | ||||||
|       }, |       }, | ||||||
|     }, |       { | ||||||
|   ], |         name: fields.textual, | ||||||
| }; |         type: "group", | ||||||
|  |         admin: { | ||||||
|  |           condition: (data: Partial<LibraryItem>) => data.itemType === LibraryItemsTypes.Textual, | ||||||
|  |         }, | ||||||
|  |         fields: [ | ||||||
|  |           { | ||||||
|  |             type: "row", | ||||||
|  |             fields: [ | ||||||
|  |               { | ||||||
|  |                 name: fields.textualSubtype, | ||||||
|  |                 label: "Subtype", | ||||||
|  |                 type: "relationship", | ||||||
|  |                 relationTo: [Keys.slug], | ||||||
|  |                 filterOptions: { type: { equals: KeysTypes.LibraryTextual } }, | ||||||
|  |                 hasMany: true, | ||||||
|  |                 admin: { allowCreate: false, width: "50%" }, | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 name: fields.textualLanguages, | ||||||
|  |                 type: "relationship", | ||||||
|  |                 relationTo: [Languages.slug], | ||||||
|  |                 hasMany: true, | ||||||
|  |                 admin: { allowCreate: false, width: "50%" }, | ||||||
|  |               }, | ||||||
|  |             ], | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             type: "row", | ||||||
|  |             fields: [ | ||||||
|  |               { name: fields.textualPageCount, type: "number", min: 1, admin: { width: "33%" } }, | ||||||
|  |               { | ||||||
|  |                 name: fields.textualBindingType, | ||||||
|  |                 label: "Binding Type", | ||||||
|  |                 type: "radio", | ||||||
|  |                 options: Object.entries(LibraryItemsTextualBindingTypes).map(([value, label]) => ({ | ||||||
|  |                   label, | ||||||
|  |                   value, | ||||||
|  |                 })), | ||||||
|  |                 admin: { | ||||||
|  |                   layout: "horizontal", | ||||||
|  |                   width: "33%", | ||||||
|  |                 }, | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 name: fields.textualPageOrder, | ||||||
|  |                 label: "Page Order", | ||||||
|  |                 type: "radio", | ||||||
|  |                 options: Object.entries(LibraryItemsTextualPageOrders).map(([value, label]) => ({ | ||||||
|  |                   label, | ||||||
|  |                   value, | ||||||
|  |                 })), | ||||||
|  |                 admin: { | ||||||
|  |                   layout: "horizontal", | ||||||
|  |                   width: "33%", | ||||||
|  |                 }, | ||||||
|  |               }, | ||||||
|  |             ], | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: fields.audio, | ||||||
|  |         type: "group", | ||||||
|  |         admin: { | ||||||
|  |           condition: (data: Partial<LibraryItem>) => data.itemType === LibraryItemsTypes.Audio, | ||||||
|  |         }, | ||||||
|  |         fields: [ | ||||||
|  |           { | ||||||
|  |             type: "row", | ||||||
|  |             fields: [ | ||||||
|  |               { | ||||||
|  |                 name: fields.audioSubtype, | ||||||
|  |                 label: "Subtype", | ||||||
|  |                 type: "relationship", | ||||||
|  |                 relationTo: [Keys.slug], | ||||||
|  |                 filterOptions: { type: { equals: KeysTypes.LibraryAudio } }, | ||||||
|  |                 hasMany: true, | ||||||
|  |                 admin: { allowCreate: false, width: "50%" }, | ||||||
|  |               }, | ||||||
|  |             ], | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: fields.releaseDate, | ||||||
|  |         type: "date", | ||||||
|  |         admin: { | ||||||
|  |           date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" }, | ||||||
|  |           position: "sidebar", | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |   }) | ||||||
|  | ); | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import { CollectionConfig } from "payload/types"; |  | ||||||
| import { CollectionGroup } from "../../constants"; | import { CollectionGroup } from "../../constants"; | ||||||
| import { collectionSlug } from "../../utils/string"; | import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   filename: "filename", |   filename: "filename", | ||||||
| @ -8,45 +7,41 @@ const fields = { | |||||||
|   filesize: "filesize", |   filesize: "filesize", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { | export const PostThumbnails = buildCollectionConfig( | ||||||
|   singular: "Post Thumbnail", |   { | ||||||
|   plural: "Post Thumbnails", |     singular: "Post Thumbnail", | ||||||
| } as const satisfies { singular: string; plural: string }; |     plural: "Post Thumbnails", | ||||||
| 
 |  | ||||||
| export const PostThumbnails: CollectionConfig = { |  | ||||||
|   slug: collectionSlug(labels.plural), |  | ||||||
|   labels, |  | ||||||
|   typescript: { interface: labels.singular }, |  | ||||||
|   defaultSort: fields.filename, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.filename, |  | ||||||
|     group: CollectionGroup.Media, |  | ||||||
|   }, |   }, | ||||||
| 
 |   ({ labels }) => ({ | ||||||
|   upload: { |     defaultSort: fields.filename, | ||||||
|     staticDir: `../uploads/${labels.plural}`, |     admin: { | ||||||
|     mimeTypes: ["image/*"], |       useAsTitle: fields.filename, | ||||||
|     imageSizes: [ |       group: CollectionGroup.Media, | ||||||
|       { |     }, | ||||||
|         name: "og", |     upload: { | ||||||
|         height: 750, |       staticDir: `../uploads/${labels.plural}`, | ||||||
|         width: 1125, |       mimeTypes: ["image/*"], | ||||||
|         formatOptions: { |       imageSizes: [ | ||||||
|           format: "jpg", |         { | ||||||
|           options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, |           name: "og", | ||||||
|  |           height: 750, | ||||||
|  |           width: 1125, | ||||||
|  |           formatOptions: { | ||||||
|  |             format: "jpg", | ||||||
|  |             options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||||
|  |           }, | ||||||
|         }, |         }, | ||||||
|       }, |         { | ||||||
|       { |           name: "medium", | ||||||
|         name: "medium", |           height: 1000, | ||||||
|         height: 1000, |           width: 1500, | ||||||
|         width: 1500, |           formatOptions: { | ||||||
|         formatOptions: { |             format: "webp", | ||||||
|           format: "webp", |             options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||||
|           options: { effort: 6, quality: 80, alphaQuality: 80 }, |           }, | ||||||
|         }, |         }, | ||||||
|       }, |       ], | ||||||
|     ], |     }, | ||||||
|   }, |     fields: [], | ||||||
| 
 |   }) | ||||||
|   fields: [], | ); | ||||||
| }; |  | ||||||
|  | |||||||
| @ -1,4 +1,3 @@ | |||||||
| import { CollectionConfig } from "payload/types"; |  | ||||||
| import { slugField } from "../../fields/slugField/slugField"; | import { slugField } from "../../fields/slugField/slugField"; | ||||||
| import { imageField } from "../../fields/imageField/imageField"; | import { imageField } from "../../fields/imageField/imageField"; | ||||||
| import { CollectionGroup, KeysTypes } from "../../constants"; | import { CollectionGroup, KeysTypes } from "../../constants"; | ||||||
| @ -7,8 +6,8 @@ import { localizedFields } from "../../fields/translatedFields/translatedFields" | |||||||
| import { isDefined, isUndefined } from "../../utils/asserts"; | import { isDefined, isUndefined } from "../../utils/asserts"; | ||||||
| import { removeTranslatorsForTranscripts } from "./hooks/beforeValidate"; | import { removeTranslatorsForTranscripts } from "./hooks/beforeValidate"; | ||||||
| import { Keys } from "../Keys/Keys"; | import { Keys } from "../Keys/Keys"; | ||||||
| import { collectionSlug } from "../../utils/string"; |  | ||||||
| import { PostThumbnails } from "../PostThumbnails/PostThumbnails"; | import { PostThumbnails } from "../PostThumbnails/PostThumbnails"; | ||||||
|  | import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   slug: "slug", |   slug: "slug", | ||||||
| @ -26,133 +25,135 @@ const fields = { | |||||||
|   proofreaders: "proofreaders", |   proofreaders: "proofreaders", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { | export const Posts = buildVersionedCollectionConfig( | ||||||
|   singular: "Post", |   { | ||||||
|   plural: "Posts", |     singular: "Post", | ||||||
| } as const satisfies { singular: string; plural: string }; |     plural: "Posts", | ||||||
| 
 |  | ||||||
| export const Posts: CollectionConfig = { |  | ||||||
|   slug: collectionSlug(labels.plural), |  | ||||||
|   labels, |  | ||||||
|   typescript: { interface: labels.singular }, |  | ||||||
|   defaultSort: fields.slug, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.slug, |  | ||||||
|     defaultColumns: [fields.slug, fields.thumbnail, fields.categories], |  | ||||||
|     group: CollectionGroup.Collections, |  | ||||||
|     preview: (doc) => `https://accords-library.com/news/${doc.slug}`, |  | ||||||
|   }, |   }, | ||||||
|   hooks: { |   () => ({ | ||||||
|     beforeValidate: [removeTranslatorsForTranscripts], |     defaultSort: fields.slug, | ||||||
|   }, |     admin: { | ||||||
|   timestamps: true, |       useAsTitle: fields.slug, | ||||||
|   versions: { drafts: { autosave: true } }, |       description: | ||||||
|   fields: [ |         "News articles written by our Recorders! Here you will find announcements about \ | ||||||
|     { | new merch/items releases, guides, theories, unboxings, showcases...", | ||||||
|       type: "row", |       defaultColumns: [fields.slug, fields.thumbnail, fields.categories], | ||||||
|       fields: [ |       group: CollectionGroup.Collections, | ||||||
|         slugField({ name: fields.slug, admin: { width: "50%" } }), |       preview: (doc) => `https://accords-library.com/news/${doc.slug}`, | ||||||
|         imageField({ |  | ||||||
|           name: fields.thumbnail, |  | ||||||
|           relationTo: PostThumbnails.slug, |  | ||||||
|           admin: { width: "50%" }, |  | ||||||
|         }), |  | ||||||
|       ], |  | ||||||
|     }, |     }, | ||||||
|     { |     hooks: { | ||||||
|       type: "row", |       beforeValidate: [removeTranslatorsForTranscripts], | ||||||
|       fields: [ |  | ||||||
|         { |  | ||||||
|           name: fields.authors, |  | ||||||
|           type: "relationship", |  | ||||||
|           relationTo: [Recorders.slug], |  | ||||||
|           required: true, |  | ||||||
|           minRows: 1, |  | ||||||
|           hasMany: true, |  | ||||||
|           admin: { width: "35%" }, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           name: fields.categories, |  | ||||||
|           type: "relationship", |  | ||||||
|           relationTo: [Keys.slug], |  | ||||||
|           filterOptions: { type: { equals: KeysTypes.Categories } }, |  | ||||||
|           hasMany: true, |  | ||||||
|           admin: { allowCreate: false, width: "35%" }, |  | ||||||
|         }, |  | ||||||
|       ], |  | ||||||
|     }, |     }, | ||||||
|     localizedFields({ |     fields: [ | ||||||
|       name: fields.translations, |       { | ||||||
|       admin: { useAsTitle: fields.title, hasSourceLanguage: true }, |         type: "row", | ||||||
|       required: true, |         fields: [ | ||||||
|       minRows: 1, |           slugField({ name: fields.slug, admin: { width: "50%" } }), | ||||||
|       fields: [ |           imageField({ | ||||||
|         { name: fields.title, type: "text", required: true }, |             name: fields.thumbnail, | ||||||
|         { name: fields.summary, type: "textarea" }, |             relationTo: PostThumbnails.slug, | ||||||
|         { |             admin: { width: "50%" }, | ||||||
|           type: "row", |           }), | ||||||
|           fields: [ |         ], | ||||||
|             { |       }, | ||||||
|               name: fields.translators, |       { | ||||||
|               type: "relationship", |         type: "row", | ||||||
|               relationTo: Recorders.slug, |         fields: [ | ||||||
|               hasMany: true, |           { | ||||||
|               admin: { |             name: fields.authors, | ||||||
|                 condition: (_, siblingData) => { |             type: "relationship", | ||||||
|  |             relationTo: [Recorders.slug], | ||||||
|  |             required: true, | ||||||
|  |             minRows: 1, | ||||||
|  |             hasMany: true, | ||||||
|  |             admin: { width: "35%" }, | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: fields.categories, | ||||||
|  |             type: "relationship", | ||||||
|  |             relationTo: [Keys.slug], | ||||||
|  |             filterOptions: { type: { equals: KeysTypes.Categories } }, | ||||||
|  |             hasMany: true, | ||||||
|  |             admin: { allowCreate: false, width: "35%" }, | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |       localizedFields({ | ||||||
|  |         name: fields.translations, | ||||||
|  |         admin: { useAsTitle: fields.title, hasSourceLanguage: true }, | ||||||
|  |         required: true, | ||||||
|  |         minRows: 1, | ||||||
|  |         fields: [ | ||||||
|  |           { name: fields.title, type: "text", required: true }, | ||||||
|  |           { name: fields.summary, type: "textarea" }, | ||||||
|  |           { | ||||||
|  |             type: "row", | ||||||
|  |             fields: [ | ||||||
|  |               { | ||||||
|  |                 name: fields.translators, | ||||||
|  |                 type: "relationship", | ||||||
|  |                 relationTo: Recorders.slug, | ||||||
|  |                 hasMany: true, | ||||||
|  |                 admin: { | ||||||
|  |                   condition: (_, siblingData) => { | ||||||
|  |                     if ( | ||||||
|  |                       isUndefined(siblingData.language) || | ||||||
|  |                       isUndefined(siblingData.sourceLanguage) | ||||||
|  |                     ) { | ||||||
|  |                       return false; | ||||||
|  |                     } | ||||||
|  |                     return siblingData.language !== siblingData.sourceLanguage; | ||||||
|  |                   }, | ||||||
|  |                   width: "50%", | ||||||
|  |                 }, | ||||||
|  |                 validate: (translators, { siblingData }) => { | ||||||
|                   if ( |                   if ( | ||||||
|                     isUndefined(siblingData.language) || |                     isUndefined(siblingData.language) || | ||||||
|                     isUndefined(siblingData.sourceLanguage) |                     isUndefined(siblingData.sourceLanguage) | ||||||
|                   ) { |                   ) { | ||||||
|                     return false; |                     return true; | ||||||
|                   } |                   } | ||||||
|                   return siblingData.language !== siblingData.sourceLanguage; |                   if (siblingData.language === siblingData.sourceLanguage) { | ||||||
|  |                     return true; | ||||||
|  |                   } | ||||||
|  |                   if (isDefined(translators) && translators.length > 0) { | ||||||
|  |                     return true; | ||||||
|  |                   } | ||||||
|  |                   return "This field is required when the language is different from the source language."; | ||||||
|                 }, |                 }, | ||||||
|                 width: "50%", |  | ||||||
|               }, |               }, | ||||||
|               validate: (translators, { siblingData }) => { |               { | ||||||
|                 if (isUndefined(siblingData.language) || isUndefined(siblingData.sourceLanguage)) { |                 name: fields.proofreaders, | ||||||
|                   return true; |                 type: "relationship", | ||||||
|                 } |                 relationTo: Recorders.slug, | ||||||
|                 if (siblingData.language === siblingData.sourceLanguage) { |                 hasMany: true, | ||||||
|                   return true; |                 admin: { width: "50%" }, | ||||||
|                 } |  | ||||||
|                 if (isDefined(translators) && translators.length > 0) { |  | ||||||
|                   return true; |  | ||||||
|                 } |  | ||||||
|                 return "This field is required when the language is different from the source language."; |  | ||||||
|               }, |               }, | ||||||
|             }, |             ], | ||||||
|             { |           }, | ||||||
|               name: fields.proofreaders, |           { name: fields.content, type: "richText", admin: { hideGutter: true } }, | ||||||
|               type: "relationship", |         ], | ||||||
|               relationTo: Recorders.slug, |       }), | ||||||
|               hasMany: true, |       { | ||||||
|               admin: { width: "50%" }, |         name: fields.publishedDate, | ||||||
|             }, |         type: "date", | ||||||
|           ], |         defaultValue: new Date().toISOString(), | ||||||
|  |         admin: { | ||||||
|  |           date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" }, | ||||||
|  |           position: "sidebar", | ||||||
|         }, |         }, | ||||||
|         { name: fields.content, type: "richText", admin: { hideGutter: true } }, |         required: true, | ||||||
|       ], |  | ||||||
|     }), |  | ||||||
|     { |  | ||||||
|       name: fields.publishedDate, |  | ||||||
|       type: "date", |  | ||||||
|       defaultValue: new Date().toISOString(), |  | ||||||
|       admin: { |  | ||||||
|         date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" }, |  | ||||||
|         position: "sidebar", |  | ||||||
|       }, |       }, | ||||||
|       required: true, |       { | ||||||
|     }, |         name: fields.hidden, | ||||||
|     { |         type: "checkbox", | ||||||
|       name: fields.hidden, |         required: false, | ||||||
|       type: "checkbox", |         defaultValue: false, | ||||||
|       required: false, |         admin: { | ||||||
|       defaultValue: false, |           description: "If enabled, the post won't appear in the 'News' section", | ||||||
|       admin: { |           position: "sidebar", | ||||||
|         description: "If enabled, the post won't appear in the 'News' section", |         }, | ||||||
|         position: "sidebar", |  | ||||||
|       }, |       }, | ||||||
|     }, |     ], | ||||||
|   ], |   }) | ||||||
| }; | ); | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ export const removeTranslatorsForTranscripts: CollectionBeforeValidateHook<Post> | |||||||
|   data: { translations, ...data }, |   data: { translations, ...data }, | ||||||
| }) => ({ | }) => ({ | ||||||
|   ...data, |   ...data, | ||||||
|   translations: translations.map(({ translators, ...translation }) => { |   translations: translations?.map(({ translators, ...translation }) => { | ||||||
|     if (translation.language === translation.sourceLanguage) { |     if (translation.language === translation.sourceLanguage) { | ||||||
|       return { ...translation, translators: [] }; |       return { ...translation, translators: [] }; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import { CollectionConfig } from "payload/types"; |  | ||||||
| import { CollectionGroup } from "../../constants"; | import { CollectionGroup } from "../../constants"; | ||||||
| import { collectionSlug } from "../../utils/string"; | import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   filename: "filename", |   filename: "filename", | ||||||
| @ -8,46 +7,42 @@ const fields = { | |||||||
|   filesize: "filesize", |   filesize: "filesize", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { | export const RecorderThumbnails = buildCollectionConfig( | ||||||
|   singular: "Recorder Thumbnail", |   { | ||||||
|   plural: "Recorder Thumbnails", |     singular: "Recorder Thumbnail", | ||||||
| } as const satisfies { singular: string; plural: string }; |     plural: "Recorder Thumbnails", | ||||||
| 
 |  | ||||||
| export const RecorderThumbnails: CollectionConfig = { |  | ||||||
|   slug: collectionSlug(labels.plural), |  | ||||||
|   labels, |  | ||||||
|   typescript: { interface: labels.singular }, |  | ||||||
|   defaultSort: fields.filename, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.filename, |  | ||||||
|     group: CollectionGroup.Media, |  | ||||||
|   }, |   }, | ||||||
| 
 |   ({ labels }) => ({ | ||||||
|   upload: { |     defaultSort: fields.filename, | ||||||
|     staticDir: `../uploads/${labels.plural}`, |     admin: { | ||||||
|     adminThumbnail: "small", |       useAsTitle: fields.filename, | ||||||
|     mimeTypes: ["image/*"], |       group: CollectionGroup.Media, | ||||||
|     imageSizes: [ |     }, | ||||||
|       { |     upload: { | ||||||
|         name: "og", |       staticDir: `../uploads/${labels.plural}`, | ||||||
|         height: 256, |       adminThumbnail: "small", | ||||||
|         width: 256, |       mimeTypes: ["image/*"], | ||||||
|         formatOptions: { |       imageSizes: [ | ||||||
|           format: "jpg", |         { | ||||||
|           options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, |           name: "og", | ||||||
|  |           height: 256, | ||||||
|  |           width: 256, | ||||||
|  |           formatOptions: { | ||||||
|  |             format: "jpg", | ||||||
|  |             options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||||
|  |           }, | ||||||
|         }, |         }, | ||||||
|       }, |         { | ||||||
|       { |           name: "small", | ||||||
|         name: "small", |           height: 128, | ||||||
|         height: 128, |           width: 128, | ||||||
|         width: 128, |           formatOptions: { | ||||||
|         formatOptions: { |             format: "webp", | ||||||
|           format: "webp", |             options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||||
|           options: { effort: 6, quality: 80, alphaQuality: 80 }, |           }, | ||||||
|         }, |         }, | ||||||
|       }, |       ], | ||||||
|     ], |     }, | ||||||
|   }, |     fields: [], | ||||||
| 
 |   }) | ||||||
|   fields: [], | ); | ||||||
| }; |  | ||||||
|  | |||||||
| @ -1,11 +1,10 @@ | |||||||
| import { CollectionConfig } from "payload/types"; |  | ||||||
| import { localizedFields } from "../../fields/translatedFields/translatedFields"; | import { localizedFields } from "../../fields/translatedFields/translatedFields"; | ||||||
| import { Languages } from "../Languages"; | import { Languages } from "../Languages"; | ||||||
| import { beforeDuplicate } from "./hooks/beforeDuplicate"; | import { beforeDuplicate } from "./hooks/beforeDuplicate"; | ||||||
| import { CollectionGroup } from "../../constants"; | import { CollectionGroup } from "../../constants"; | ||||||
| import { collectionSlug } from "../../utils/string"; |  | ||||||
| import { RecorderThumbnails } from "../RecorderThumbnails/RecorderThumbnails"; | import { RecorderThumbnails } from "../RecorderThumbnails/RecorderThumbnails"; | ||||||
| import { imageField } from "../../fields/imageField/imageField"; | import { imageField } from "../../fields/imageField/imageField"; | ||||||
|  | import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|   username: "username", |   username: "username", | ||||||
| @ -16,79 +15,77 @@ const fields = { | |||||||
|   avatar: "avatar", |   avatar: "avatar", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { | export const Recorders = buildCollectionConfig( | ||||||
|   singular: "Recorder", |   { | ||||||
|   plural: "Recorders", |     singular: "Recorder", | ||||||
| } as const satisfies { singular: string; plural: string }; |     plural: "Recorders", | ||||||
| 
 |  | ||||||
| export const Recorders: CollectionConfig = { |  | ||||||
|   slug: collectionSlug(labels.plural), |  | ||||||
|   labels, |  | ||||||
|   typescript: { interface: labels.singular }, |  | ||||||
|   defaultSort: fields.username, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.username, |  | ||||||
|     hooks: { beforeDuplicate }, |  | ||||||
|     description: |  | ||||||
|       "Recorders are contributors of the Accord's Library project. Create a Recorder here to be able to credit them in other collections", |  | ||||||
|     defaultColumns: [ |  | ||||||
|       fields.username, |  | ||||||
|       fields.avatar, |  | ||||||
|       fields.anonymize, |  | ||||||
|       fields.biographies, |  | ||||||
|       fields.languages, |  | ||||||
|     ], |  | ||||||
|     group: CollectionGroup.Meta, |  | ||||||
|   }, |   }, | ||||||
|   timestamps: false, |   () => ({ | ||||||
|   fields: [ |     defaultSort: fields.username, | ||||||
|     { |     admin: { | ||||||
|       type: "row", |       useAsTitle: fields.username, | ||||||
|       fields: [ |       hooks: { beforeDuplicate }, | ||||||
|         { |       description: | ||||||
|           name: fields.username, |         "Recorders are contributors of the Accord's Library project. Create a Recorder here to be able to credit them in other collections", | ||||||
|           type: "text", |       defaultColumns: [ | ||||||
|           unique: true, |         fields.username, | ||||||
|           required: true, |         fields.avatar, | ||||||
|           admin: { description: "The username must be unique", width: "33%" }, |         fields.anonymize, | ||||||
|         }, |         fields.biographies, | ||||||
|         imageField({ |         fields.languages, | ||||||
|           name: fields.avatar, |  | ||||||
|           relationTo: RecorderThumbnails.slug, |  | ||||||
|           admin: { width: "66%" }, |  | ||||||
|         }), |  | ||||||
|       ], |       ], | ||||||
|  |       group: CollectionGroup.Meta, | ||||||
|     }, |     }, | ||||||
|     { |     timestamps: false, | ||||||
|       name: fields.languages, |     fields: [ | ||||||
|       type: "relationship", |       { | ||||||
|       relationTo: Languages.slug, |         type: "row", | ||||||
|       hasMany: true, |         fields: [ | ||||||
|       admin: { |           { | ||||||
|         allowCreate: false, |             name: fields.username, | ||||||
|         description: "List of language(s) that this recorder is familiar with", |             type: "text", | ||||||
|  |             unique: true, | ||||||
|  |             required: true, | ||||||
|  |             admin: { description: "The username must be unique", width: "33%" }, | ||||||
|  |           }, | ||||||
|  |           imageField({ | ||||||
|  |             name: fields.avatar, | ||||||
|  |             relationTo: RecorderThumbnails.slug, | ||||||
|  |             admin: { width: "66%" }, | ||||||
|  |           }), | ||||||
|  |         ], | ||||||
|       }, |       }, | ||||||
|     }, |       { | ||||||
|     localizedFields({ |         name: fields.languages, | ||||||
|       name: fields.biographies, |         type: "relationship", | ||||||
|       interfaceName: "RecorderBiographies", |         relationTo: Languages.slug, | ||||||
|       admin: { |         hasMany: true, | ||||||
|         useAsTitle: fields.biography, |         admin: { | ||||||
|         description: |           allowCreate: false, | ||||||
|           "A short personal description about you or your involvement with this project or the franchise", |           description: "List of language(s) that this recorder is familiar with", | ||||||
|  |         }, | ||||||
|       }, |       }, | ||||||
|       fields: [{ name: fields.biography, type: "textarea" }], |       localizedFields({ | ||||||
|     }), |         name: fields.biographies, | ||||||
|     { |         interfaceName: "RecorderBiographies", | ||||||
|       name: fields.anonymize, |         admin: { | ||||||
|       type: "checkbox", |           useAsTitle: fields.biography, | ||||||
|       required: true, |           description: | ||||||
|       defaultValue: false, |             "A short personal description about you or your involvement with this project or the franchise", | ||||||
|       admin: { |         }, | ||||||
|         description: |         fields: [{ name: fields.biography, type: "textarea" }], | ||||||
|           "If enabled, this recorder's username will not be made public. Instead they will be referred to as 'Recorder#0000' where '0000' is a random four digit number", |       }), | ||||||
|         position: "sidebar", |       { | ||||||
|  |         name: fields.anonymize, | ||||||
|  |         type: "checkbox", | ||||||
|  |         required: true, | ||||||
|  |         defaultValue: false, | ||||||
|  |         admin: { | ||||||
|  |           description: | ||||||
|  |             "If enabled, this recorder's username will not be made public. Instead they will be referred to as 'Recorder#0000' where '0000' is a random four digit number", | ||||||
|  |           position: "sidebar", | ||||||
|  |         }, | ||||||
|       }, |       }, | ||||||
|     }, |     ], | ||||||
|   ], |   }) | ||||||
| }; | ); | ||||||
|  | |||||||
| @ -1,27 +1,60 @@ | |||||||
| import { CollectionConfig } from "payload/types"; | import { CollectionGroup, UserRoles } from "../constants"; | ||||||
| import { CollectionGroup } from "../constants"; | import { Recorders } from "./Recorders/Recorders"; | ||||||
| import { collectionSlug } from "../utils/string"; | import { buildCollectionConfig } from "../utils/collectionConfig"; | ||||||
| 
 | 
 | ||||||
| const fields = { | const fields = { | ||||||
|  |   recorder: "recorder", | ||||||
|  |   name: "name", | ||||||
|   email: "email", |   email: "email", | ||||||
|  |   role: "role", | ||||||
| } as const satisfies Record<string, string>; | } as const satisfies Record<string, string>; | ||||||
| 
 | 
 | ||||||
| const labels = { | export const Users = buildCollectionConfig( | ||||||
|   singular: "User", |   { | ||||||
|   plural: "Users", |     singular: "User", | ||||||
| } as const satisfies { singular: string; plural: string }; |     plural: "Users", | ||||||
| 
 |  | ||||||
| export const Users: CollectionConfig = { |  | ||||||
|   slug: collectionSlug(labels.plural), |  | ||||||
|   auth: true, |  | ||||||
|   labels, |  | ||||||
|   typescript: { interface: labels.singular }, |  | ||||||
|   defaultSort: fields.email, |  | ||||||
|   admin: { |  | ||||||
|     useAsTitle: fields.email, |  | ||||||
|     defaultColumns: [fields.email], |  | ||||||
|     group: CollectionGroup.Administration, |  | ||||||
|   }, |   }, | ||||||
|   timestamps: false, |   () => ({ | ||||||
|   fields: [], |     auth: true, | ||||||
| }; |     defaultSort: fields.recorder, | ||||||
|  |     admin: { | ||||||
|  |       useAsTitle: fields.name, | ||||||
|  |       defaultColumns: [fields.recorder, fields.name, fields.email, fields.role], | ||||||
|  |       group: CollectionGroup.Administration, | ||||||
|  |     }, | ||||||
|  |     timestamps: false, | ||||||
|  |     fields: [ | ||||||
|  |       { | ||||||
|  |         type: "row", | ||||||
|  |         fields: [ | ||||||
|  |           { | ||||||
|  |             name: fields.recorder, | ||||||
|  |             type: "relationship", | ||||||
|  |             relationTo: Recorders.slug, | ||||||
|  |             required: true, | ||||||
|  |             admin: { width: "33%" }, | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: fields.name, | ||||||
|  |             type: "text", | ||||||
|  |             required: true, | ||||||
|  |             unique: true, | ||||||
|  |             admin: { width: "33%" }, | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: fields.role, | ||||||
|  |             required: true, | ||||||
|  |             defaultValue: [UserRoles.Recorder], | ||||||
|  |             type: "select", | ||||||
|  |             hasMany: true, | ||||||
|  |             options: Object.entries(UserRoles).map(([value, label]) => ({ | ||||||
|  |               label, | ||||||
|  |               value, | ||||||
|  |             })), | ||||||
|  |             admin: { width: "33%" }, | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |   }) | ||||||
|  | ); | ||||||
|  | |||||||
| @ -42,3 +42,8 @@ export enum LibraryItemsTextualPageOrders { | |||||||
|   LeftToRight = "Left to right", |   LeftToRight = "Left to right", | ||||||
|   RightToLeft = "Right to left", |   RightToLeft = "Right to left", | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export enum UserRoles { | ||||||
|  |   Admin = "Admin", | ||||||
|  |   Recorder = "Recorder", | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,11 +1,13 @@ | |||||||
| import { Props } from "payload/components/views/Cell"; | import { Props } from "payload/components/views/Cell"; | ||||||
| import { useState, useEffect } from "react"; | import { useState, useEffect } from "react"; | ||||||
| import React from "react"; | import React from "react"; | ||||||
|  | import { isUndefined } from "../../utils/asserts"; | ||||||
| 
 | 
 | ||||||
| export const Cell = ({ cellData, field }: Props): JSX.Element => { | export const Cell = ({ cellData, field }: Props): JSX.Element => { | ||||||
|   const [imageURL, setImageURL] = useState<string>(); |   const [imageURL, setImageURL] = useState<string>(); | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     const fetchUrl = async () => { |     const fetchUrl = async () => { | ||||||
|  |       if (isUndefined(cellData)) return; | ||||||
|       if (typeof cellData !== "string") return; |       if (typeof cellData !== "string") return; | ||||||
|       if (field.type !== "upload") return; |       if (field.type !== "upload") return; | ||||||
|       const result = await (await fetch(`/api/${field.relationTo}/${cellData}`)).json(); |       const result = await (await fetch(`/api/${field.relationTo}/${cellData}`)).json(); | ||||||
|  | |||||||
| @ -44,10 +44,11 @@ export const localizedFields = ({ | |||||||
|     components: { |     components: { | ||||||
|       Cell: ({ cellData }) => |       Cell: ({ cellData }) => | ||||||
|         Cell({ |         Cell({ | ||||||
|           cellData: cellData.map((row) => ({ |           cellData: | ||||||
|             language: row.language, |             cellData?.map((row) => ({ | ||||||
|             title: isDefined(useAsTitle) ? row[useAsTitle] : undefined, |               language: row.language, | ||||||
|           })), |               title: isDefined(useAsTitle) ? row[useAsTitle] : undefined, | ||||||
|  |             })) ?? [], | ||||||
|         }), |         }), | ||||||
|       RowLabel: ({ data }) => |       RowLabel: ({ data }) => | ||||||
|         RowLabel({ |         RowLabel({ | ||||||
|  | |||||||
| @ -93,6 +93,7 @@ export interface LibraryItem { | |||||||
|         }[]; |         }[]; | ||||||
|   }; |   }; | ||||||
|   releaseDate?: string; |   releaseDate?: string; | ||||||
|  |   lastModifiedBy: string | User; | ||||||
|   updatedAt: string; |   updatedAt: string; | ||||||
|   createdAt: string; |   createdAt: string; | ||||||
|   _status?: "draft" | "published"; |   _status?: "draft" | "published"; | ||||||
| @ -154,6 +155,57 @@ export interface Language { | |||||||
|   id: string; |   id: string; | ||||||
|   name: string; |   name: string; | ||||||
| } | } | ||||||
|  | export interface User { | ||||||
|  |   id: string; | ||||||
|  |   recorder: string | Recorder; | ||||||
|  |   name: string; | ||||||
|  |   role: ("Admin" | "Recorder")[]; | ||||||
|  |   email: string; | ||||||
|  |   resetPasswordToken?: string; | ||||||
|  |   resetPasswordExpiration?: string; | ||||||
|  |   salt?: string; | ||||||
|  |   hash?: string; | ||||||
|  |   loginAttempts?: number; | ||||||
|  |   lockUntil?: string; | ||||||
|  |   password?: string; | ||||||
|  | } | ||||||
|  | export interface Recorder { | ||||||
|  |   id: string; | ||||||
|  |   username: string; | ||||||
|  |   avatar?: string | RecorderThumbnail; | ||||||
|  |   languages?: string[] | Language[]; | ||||||
|  |   biographies?: RecorderBiographies; | ||||||
|  |   anonymize: boolean; | ||||||
|  | } | ||||||
|  | export interface RecorderThumbnail { | ||||||
|  |   id: string; | ||||||
|  |   updatedAt: string; | ||||||
|  |   createdAt: string; | ||||||
|  |   url?: string; | ||||||
|  |   filename?: string; | ||||||
|  |   mimeType?: string; | ||||||
|  |   filesize?: number; | ||||||
|  |   width?: number; | ||||||
|  |   height?: number; | ||||||
|  |   sizes?: { | ||||||
|  |     og?: { | ||||||
|  |       url?: string; | ||||||
|  |       width?: number; | ||||||
|  |       height?: number; | ||||||
|  |       mimeType?: string; | ||||||
|  |       filesize?: number; | ||||||
|  |       filename?: string; | ||||||
|  |     }; | ||||||
|  |     small?: { | ||||||
|  |       url?: string; | ||||||
|  |       width?: number; | ||||||
|  |       height?: number; | ||||||
|  |       mimeType?: string; | ||||||
|  |       filesize?: number; | ||||||
|  |       filename?: string; | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  | } | ||||||
| export interface Content { | export interface Content { | ||||||
|   id: string; |   id: string; | ||||||
|   slug: string; |   slug: string; | ||||||
| @ -188,6 +240,7 @@ export interface Content { | |||||||
|     audio?: string | File; |     audio?: string | File; | ||||||
|     id?: string; |     id?: string; | ||||||
|   }[]; |   }[]; | ||||||
|  |   lastModifiedBy: string | User; | ||||||
|   updatedAt: string; |   updatedAt: string; | ||||||
|   createdAt: string; |   createdAt: string; | ||||||
|   _status?: "draft" | "published"; |   _status?: "draft" | "published"; | ||||||
| @ -221,43 +274,6 @@ export interface ContentThumbnail { | |||||||
|     }; |     }; | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| export interface Recorder { |  | ||||||
|   id: string; |  | ||||||
|   username: string; |  | ||||||
|   avatar?: string | RecorderThumbnail; |  | ||||||
|   languages?: string[] | Language[]; |  | ||||||
|   biographies?: RecorderBiographies; |  | ||||||
|   anonymize: boolean; |  | ||||||
| } |  | ||||||
| export interface RecorderThumbnail { |  | ||||||
|   id: string; |  | ||||||
|   updatedAt: string; |  | ||||||
|   createdAt: string; |  | ||||||
|   url?: string; |  | ||||||
|   filename?: string; |  | ||||||
|   mimeType?: string; |  | ||||||
|   filesize?: number; |  | ||||||
|   width?: number; |  | ||||||
|   height?: number; |  | ||||||
|   sizes?: { |  | ||||||
|     og?: { |  | ||||||
|       url?: string; |  | ||||||
|       width?: number; |  | ||||||
|       height?: number; |  | ||||||
|       mimeType?: string; |  | ||||||
|       filesize?: number; |  | ||||||
|       filename?: string; |  | ||||||
|     }; |  | ||||||
|     small?: { |  | ||||||
|       url?: string; |  | ||||||
|       width?: number; |  | ||||||
|       height?: number; |  | ||||||
|       mimeType?: string; |  | ||||||
|       filesize?: number; |  | ||||||
|       filename?: string; |  | ||||||
|     }; |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| export interface TextBlock { | export interface TextBlock { | ||||||
|   content: { |   content: { | ||||||
|     [k: string]: unknown; |     [k: string]: unknown; | ||||||
| @ -511,6 +527,7 @@ export interface Post { | |||||||
|   }[]; |   }[]; | ||||||
|   publishedDate: string; |   publishedDate: string; | ||||||
|   hidden?: boolean; |   hidden?: boolean; | ||||||
|  |   lastModifiedBy: string | User; | ||||||
|   updatedAt: string; |   updatedAt: string; | ||||||
|   createdAt: string; |   createdAt: string; | ||||||
|   _status?: "draft" | "published"; |   _status?: "draft" | "published"; | ||||||
| @ -544,14 +561,3 @@ export interface PostThumbnail { | |||||||
|     }; |     }; | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| export interface User { |  | ||||||
|   id: string; |  | ||||||
|   email: string; |  | ||||||
|   resetPasswordToken?: string; |  | ||||||
|   resetPasswordExpiration?: string; |  | ||||||
|   salt?: string; |  | ||||||
|   hash?: string; |  | ||||||
|   loginAttempts?: number; |  | ||||||
|   lockUntil?: string; |  | ||||||
|   password?: string; |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										22
									
								
								src/utils/collectionConfig.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/utils/collectionConfig.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | import { CollectionConfig } from "payload/types"; | ||||||
|  | import slugify from "slugify"; | ||||||
|  | 
 | ||||||
|  | export type BuildCollectionConfig = Omit<CollectionConfig, "slug" | "typescript" | "labels">; | ||||||
|  | 
 | ||||||
|  | export type GenerationFunctionProps = { | ||||||
|  |   labels: { singular: string; plural: string }; | ||||||
|  |   slug: string; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const buildCollectionConfig = ( | ||||||
|  |   labels: { singular: string; plural: string }, | ||||||
|  |   generationFunction: (props: GenerationFunctionProps) => BuildCollectionConfig | ||||||
|  | ): CollectionConfig => { | ||||||
|  |   const slug = slugify(labels.plural, { lower: true, strict: true, trim: true }); | ||||||
|  |   const config = generationFunction({ labels, slug }); | ||||||
|  |   return { | ||||||
|  |     ...config, | ||||||
|  |     slug, | ||||||
|  |     typescript: { interface: labels.singular }, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
| @ -1,11 +1,7 @@ | |||||||
| import ISO6391 from "iso-639-1"; | import ISO6391 from "iso-639-1"; | ||||||
| import slugify from "slugify"; |  | ||||||
| 
 | 
 | ||||||
| export const shortenEllipsis = (text: string, length: number): string => | export const shortenEllipsis = (text: string, length: number): string => | ||||||
|   text.length - 3 > length ? `${text.substring(0, length)}...` : text; |   text.length - 3 > length ? `${text.substring(0, length)}...` : text; | ||||||
| 
 | 
 | ||||||
| export const formatLanguageCode = (code: string): string => | export const formatLanguageCode = (code: string): string => | ||||||
|   ISO6391.validate(code) ? ISO6391.getName(code) : code; |   ISO6391.validate(code) ? ISO6391.getName(code) : code; | ||||||
| 
 |  | ||||||
| export const collectionSlug = (text: string): string => |  | ||||||
|   slugify(text, { lower: true, strict: true, trim: true }); |  | ||||||
|  | |||||||
							
								
								
									
										52
									
								
								src/utils/versionedCollectionConfig.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/utils/versionedCollectionConfig.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | import { CollectionBeforeChangeHook, CollectionConfig, RelationshipField } from "payload/types"; | ||||||
|  | import { Users } from "../collections/Users"; | ||||||
|  | import { | ||||||
|  |   BuildCollectionConfig, | ||||||
|  |   GenerationFunctionProps, | ||||||
|  |   buildCollectionConfig, | ||||||
|  | } from "./collectionConfig"; | ||||||
|  | 
 | ||||||
|  | const fields = { lastModifiedBy: "lastModifiedBy" }; | ||||||
|  | 
 | ||||||
|  | const beforeChangeLastModifiedBy: CollectionBeforeChangeHook = async ({ | ||||||
|  |   data: { updatedBy, ...data }, | ||||||
|  |   req, | ||||||
|  | }) => { | ||||||
|  |   console.log(data, req.user); | ||||||
|  |   return { | ||||||
|  |     ...data, | ||||||
|  |     [fields.lastModifiedBy]: req.user.id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const lastModifiedByField = (): RelationshipField => ({ | ||||||
|  |   name: fields.lastModifiedBy, | ||||||
|  |   type: "relationship", | ||||||
|  |   required: true, | ||||||
|  |   relationTo: Users.slug, | ||||||
|  |   admin: { readOnly: true, position: "sidebar" }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | type BuildVersionedCollectionConfig = Omit<BuildCollectionConfig, "timestamps" | "versions">; | ||||||
|  | 
 | ||||||
|  | export const buildVersionedCollectionConfig = ( | ||||||
|  |   labels: { singular: string; plural: string }, | ||||||
|  |   generationFunction: (props: GenerationFunctionProps) => BuildVersionedCollectionConfig | ||||||
|  | ): CollectionConfig => { | ||||||
|  |   const { | ||||||
|  |     hooks: { beforeChange, ...otherHooks } = {}, | ||||||
|  |     fields, | ||||||
|  |     ...otherParams | ||||||
|  |   } = buildCollectionConfig(labels, generationFunction); | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     ...otherParams, | ||||||
|  |     timestamps: true, | ||||||
|  |     versions: { drafts: { autosave: { interval: 2000 } } }, | ||||||
|  |     hooks: { | ||||||
|  |       ...otherHooks, | ||||||
|  |       beforeChange: [...(beforeChange ?? []), beforeChangeLastModifiedBy], | ||||||
|  |     }, | ||||||
|  |     fields: [...fields, lastModifiedByField()], | ||||||
|  |   }; | ||||||
|  | }; | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint