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 { CollectionGroup, KeysTypes } from "../../constants"; | ||||
| import { CollectionGroup } from "../../constants"; | ||||
| import { localizedFields } from "../../fields/translatedFields/translatedFields"; | ||||
| import { collectionSlug } from "../../utils/string"; | ||||
| import { Contents } from "../Contents/Contents"; | ||||
| import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   slug: "slug", | ||||
| @ -13,53 +12,49 @@ const fields = { | ||||
|   contents: "contents", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| const labels = { | ||||
|   singular: "Content Folder", | ||||
|   plural: "Content Folders", | ||||
| } as const satisfies { singular: string; plural: string }; | ||||
| 
 | ||||
| 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, | ||||
| export const ContentFolders = buildCollectionConfig( | ||||
|   { | ||||
|     singular: "Content Folder", | ||||
|     plural: "Content Folders", | ||||
|   }, | ||||
|   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%" }, | ||||
|         }, | ||||
|       ], | ||||
|   ({ slug }) => ({ | ||||
|     defaultSort: fields.slug, | ||||
|     admin: { | ||||
|       useAsTitle: fields.slug, | ||||
|       defaultColumns: [fields.slug, fields.translations], | ||||
|       group: CollectionGroup.Collections, | ||||
|     }, | ||||
|   ], | ||||
| }; | ||||
|     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 { collectionSlug } from "../../utils/string"; | ||||
| import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   filename: "filename", | ||||
| @ -8,45 +7,41 @@ const fields = { | ||||
|   filesize: "filesize", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| const labels = { | ||||
|   singular: "Content Thumbnail", | ||||
|   plural: "Content Thumbnails", | ||||
| } as const satisfies { singular: string; plural: string }; | ||||
| 
 | ||||
| export const ContentThumbnails: CollectionConfig = { | ||||
|   slug: collectionSlug(labels.plural), | ||||
|   labels, | ||||
|   typescript: { interface: labels.singular }, | ||||
|   defaultSort: fields.filename, | ||||
|   admin: { | ||||
|     useAsTitle: fields.filename, | ||||
|     group: CollectionGroup.Media, | ||||
| export const ContentThumbnails = buildCollectionConfig( | ||||
|   { | ||||
|     singular: "Content Thumbnail", | ||||
|     plural: "Content Thumbnails", | ||||
|   }, | ||||
| 
 | ||||
|   upload: { | ||||
|     staticDir: `../uploads/${labels.plural}`, | ||||
|     mimeTypes: ["image/*"], | ||||
|     imageSizes: [ | ||||
|       { | ||||
|         name: "og", | ||||
|         height: 750, | ||||
|         width: 1125, | ||||
|         formatOptions: { | ||||
|           format: "jpg", | ||||
|           options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||
|   ({ labels }) => ({ | ||||
|     defaultSort: fields.filename, | ||||
|     admin: { | ||||
|       useAsTitle: fields.filename, | ||||
|       group: CollectionGroup.Media, | ||||
|     }, | ||||
|     upload: { | ||||
|       staticDir: `../uploads/${labels.plural}`, | ||||
|       mimeTypes: ["image/*"], | ||||
|       imageSizes: [ | ||||
|         { | ||||
|           name: "og", | ||||
|           height: 750, | ||||
|           width: 1125, | ||||
|           formatOptions: { | ||||
|             format: "jpg", | ||||
|             options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         name: "medium", | ||||
|         height: 1000, | ||||
|         width: 1500, | ||||
|         formatOptions: { | ||||
|           format: "webp", | ||||
|           options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||
|         { | ||||
|           name: "medium", | ||||
|           height: 1000, | ||||
|           width: 1500, | ||||
|           formatOptions: { | ||||
|             format: "webp", | ||||
|             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 { slugField } from "../../fields/slugField/slugField"; | ||||
| import { imageField } from "../../fields/imageField/imageField"; | ||||
| @ -10,6 +8,7 @@ import { isDefined } from "../../utils/asserts"; | ||||
| import { fileField } from "../../fields/fileField/fileField"; | ||||
| import { contentBlocks } from "./Blocks/blocks"; | ||||
| import { ContentThumbnails } from "../ContentThumbnails/ContentThumbnails"; | ||||
| import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   slug: "slug", | ||||
| @ -31,187 +30,186 @@ const fields = { | ||||
|   audio: "audio", | ||||
|   audioNotes: "videoNotes", | ||||
|   status: "status", | ||||
|   updatedBy: "updatedBy", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| const labels = { | ||||
|   singular: "Content", | ||||
|   plural: "Contents", | ||||
| } as const satisfies { singular: string; plural: string }; | ||||
| 
 | ||||
| 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}`, | ||||
| export const Contents = buildVersionedCollectionConfig( | ||||
|   { | ||||
|     singular: "Content", | ||||
|     plural: "Contents", | ||||
|   }, | ||||
|   timestamps: true, | ||||
|   versions: { drafts: { autosave: true } }, | ||||
|   fields: [ | ||||
|     { | ||||
|       type: "row", | ||||
|       fields: [ | ||||
|         slugField({ name: fields.slug, admin: { width: "50%" } }), | ||||
|         imageField({ | ||||
|           name: fields.thumbnail, | ||||
|           relationTo: ContentThumbnails.slug, | ||||
|           admin: { width: "50%" }, | ||||
|         }), | ||||
|   () => ({ | ||||
|     defaultSort: fields.slug, | ||||
|     admin: { | ||||
|       useAsTitle: fields.slug, | ||||
|       description: | ||||
|         "All the contents (textual, audio, and video) from the Library or other online sources.", | ||||
|       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}`, | ||||
|     }, | ||||
|     { | ||||
|       type: "row", | ||||
|       fields: [ | ||||
|         { | ||||
|           name: fields.categories, | ||||
|           type: "relationship", | ||||
|           relationTo: [Keys.slug], | ||||
|           filterOptions: { type: { equals: KeysTypes.Categories } }, | ||||
|           hasMany: true, | ||||
|           admin: { allowCreate: false, width: "50%" }, | ||||
|         }, | ||||
|         { | ||||
|           name: fields.type, | ||||
|           type: "relationship", | ||||
|           relationTo: [Keys.slug], | ||||
|           filterOptions: { type: { equals: KeysTypes.Contents } }, | ||||
|           admin: { allowCreate: false, width: "50%" }, | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     localizedFields({ | ||||
|       name: fields.translations, | ||||
|       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), | ||||
|     fields: [ | ||||
|       { | ||||
|         type: "row", | ||||
|         fields: [ | ||||
|           slugField({ name: fields.slug, admin: { width: "50%" } }), | ||||
|           imageField({ | ||||
|             name: fields.thumbnail, | ||||
|             relationTo: ContentThumbnails.slug, | ||||
|             admin: { width: "50%" }, | ||||
|           }), | ||||
|         ], | ||||
|       }, | ||||
|       { | ||||
|         type: "row", | ||||
|         fields: [ | ||||
|           { | ||||
|             name: fields.categories, | ||||
|             type: "relationship", | ||||
|             relationTo: [Keys.slug], | ||||
|             filterOptions: { type: { equals: KeysTypes.Categories } }, | ||||
|             hasMany: true, | ||||
|             admin: { allowCreate: false, width: "50%" }, | ||||
|           }, | ||||
|           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.type, | ||||
|             type: "relationship", | ||||
|             relationTo: [Keys.slug], | ||||
|             filterOptions: { type: { equals: KeysTypes.Contents } }, | ||||
|             admin: { allowCreate: false, width: "50%" }, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|       localizedFields({ | ||||
|         name: fields.translations, | ||||
|         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: [ | ||||
|               { | ||||
|                 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, | ||||
|                       label: "Translators", | ||||
|                       type: "relationship", | ||||
|                       relationTo: Recorders.slug, | ||||
|                       hasMany: true, | ||||
|                       admin: { | ||||
|                         condition: (_, siblingData) => | ||||
|                           siblingData.language !== siblingData.sourceLanguage, | ||||
|                         width: "50%", | ||||
|                       { | ||||
|                         name: fields.textTranslators, | ||||
|                         label: "Translators", | ||||
|                         type: "relationship", | ||||
|                         relationTo: Recorders.slug, | ||||
|                         hasMany: true, | ||||
|                         admin: { | ||||
|                           condition: (_, siblingData) => | ||||
|                             siblingData.language !== siblingData.sourceLanguage, | ||||
|                           width: "50%", | ||||
|                         }, | ||||
|                       }, | ||||
|                     }, | ||||
|                     { | ||||
|                       name: fields.textProofreaders, | ||||
|                       label: "Proofreaders", | ||||
|                       type: "relationship", | ||||
|                       relationTo: Recorders.slug, | ||||
|                       hasMany: true, | ||||
|                       admin: { width: "50%" }, | ||||
|                     }, | ||||
|                   ], | ||||
|                 }, | ||||
|                 { | ||||
|                   name: fields.textContent, | ||||
|                   label: "Content", | ||||
|                   labels: { singular: "Block", plural: "Blocks" }, | ||||
|                   type: "blocks", | ||||
|                   admin: { initCollapsed: true }, | ||||
|                   blocks: contentBlocks, | ||||
|                 }, | ||||
|                 { | ||||
|                   name: fields.textNotes, | ||||
|                   label: "Notes", | ||||
|                   type: "textarea", | ||||
|                 }, | ||||
|               ], | ||||
|             }, | ||||
|             { | ||||
|               label: "Video", | ||||
|               fields: [ | ||||
|                 { | ||||
|                   type: "row", | ||||
|                   fields: [ | ||||
|                     fileField({ | ||||
|                       name: fields.video, | ||||
|                       filterOptions: { type: { equals: FileTypes.ContentVideo } }, | ||||
|                       admin: { width: "50%" }, | ||||
|                     }), | ||||
|                     { | ||||
|                       name: fields.videoNotes, | ||||
|                       label: "Notes", | ||||
|                       type: "textarea", | ||||
|                       admin: { width: "50%" }, | ||||
|                     }, | ||||
|                   ], | ||||
|                 }, | ||||
|               ], | ||||
|             }, | ||||
|             { | ||||
|               label: "Audio", | ||||
|               fields: [ | ||||
|                 { | ||||
|                   type: "row", | ||||
|                   fields: [ | ||||
|                     fileField({ | ||||
|                       name: fields.audio, | ||||
|                       filterOptions: { type: { equals: FileTypes.ContentAudio } }, | ||||
|                       admin: { width: "50%" }, | ||||
|                     }), | ||||
|                     { | ||||
|                       name: fields.audioNotes, | ||||
|                       label: "Notes", | ||||
|                       type: "textarea", | ||||
|                       admin: { width: "50%" }, | ||||
|                     }, | ||||
|                   ], | ||||
|                 }, | ||||
|               ], | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|       ], | ||||
|     }), | ||||
|   ], | ||||
| }; | ||||
|                       { | ||||
|                         name: fields.textProofreaders, | ||||
|                         label: "Proofreaders", | ||||
|                         type: "relationship", | ||||
|                         relationTo: Recorders.slug, | ||||
|                         hasMany: true, | ||||
|                         admin: { width: "50%" }, | ||||
|                       }, | ||||
|                     ], | ||||
|                   }, | ||||
|                   { | ||||
|                     name: fields.textContent, | ||||
|                     label: "Content", | ||||
|                     labels: { singular: "Block", plural: "Blocks" }, | ||||
|                     type: "blocks", | ||||
|                     admin: { initCollapsed: true }, | ||||
|                     blocks: contentBlocks, | ||||
|                   }, | ||||
|                   { | ||||
|                     name: fields.textNotes, | ||||
|                     label: "Notes", | ||||
|                     type: "textarea", | ||||
|                   }, | ||||
|                 ], | ||||
|               }, | ||||
|               { | ||||
|                 label: "Video", | ||||
|                 fields: [ | ||||
|                   { | ||||
|                     type: "row", | ||||
|                     fields: [ | ||||
|                       fileField({ | ||||
|                         name: fields.video, | ||||
|                         filterOptions: { type: { equals: FileTypes.ContentVideo } }, | ||||
|                         admin: { width: "50%" }, | ||||
|                       }), | ||||
|                       { | ||||
|                         name: fields.videoNotes, | ||||
|                         label: "Notes", | ||||
|                         type: "textarea", | ||||
|                         admin: { width: "50%" }, | ||||
|                       }, | ||||
|                     ], | ||||
|                   }, | ||||
|                 ], | ||||
|               }, | ||||
|               { | ||||
|                 label: "Audio", | ||||
|                 fields: [ | ||||
|                   { | ||||
|                     type: "row", | ||||
|                     fields: [ | ||||
|                       fileField({ | ||||
|                         name: fields.audio, | ||||
|                         filterOptions: { type: { equals: FileTypes.ContentAudio } }, | ||||
|                         admin: { width: "50%" }, | ||||
|                       }), | ||||
|                       { | ||||
|                         name: fields.audioNotes, | ||||
|                         label: "Notes", | ||||
|                         type: "textarea", | ||||
|                         admin: { width: "50%" }, | ||||
|                       }, | ||||
|                     ], | ||||
|                   }, | ||||
|                 ], | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
|         ], | ||||
|       }), | ||||
|     ], | ||||
|   }) | ||||
| ); | ||||
|  | ||||
| @ -1,38 +1,34 @@ | ||||
| import { CollectionConfig } from "payload/types"; | ||||
| import { CollectionGroup, FileTypes } from "../../constants"; | ||||
| import { collectionSlug } from "../../utils/string"; | ||||
| import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   filename: "filename", | ||||
|   type: "type", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| const labels = { | ||||
|   singular: "File", | ||||
|   plural: "Files", | ||||
| } as const satisfies { singular: string; plural: string }; | ||||
| 
 | ||||
| export const Files: CollectionConfig = { | ||||
|   slug: collectionSlug(labels.plural), | ||||
|   labels, | ||||
|   typescript: { interface: labels.singular }, | ||||
|   defaultSort: fields.filename, | ||||
|   admin: { | ||||
|     useAsTitle: fields.filename, | ||||
|     group: CollectionGroup.Media, | ||||
| export const Files = buildCollectionConfig( | ||||
|   { | ||||
|     singular: "File", | ||||
|     plural: "Files", | ||||
|   }, | ||||
| 
 | ||||
|   fields: [ | ||||
|     { | ||||
|       name: fields.filename, | ||||
|       required: true, | ||||
|       type: "text", | ||||
|   () => ({ | ||||
|     defaultSort: fields.filename, | ||||
|     admin: { | ||||
|       useAsTitle: fields.filename, | ||||
|       group: CollectionGroup.Media, | ||||
|     }, | ||||
|     { | ||||
|       name: fields.type, | ||||
|       type: "select", | ||||
|       required: true, | ||||
|       options: Object.entries(FileTypes).map(([value, label]) => ({ label, value })), | ||||
|     }, | ||||
|   ], | ||||
| }; | ||||
|     fields: [ | ||||
|       { | ||||
|         name: fields.filename, | ||||
|         required: true, | ||||
|         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 { CollectionGroup, KeysTypes } from "../../constants"; | ||||
| import { localizedFields } from "../../fields/translatedFields/translatedFields"; | ||||
| import { collectionSlug } from "../../utils/string"; | ||||
| import { Key } from "../../types/collections"; | ||||
| import { isDefined } from "../../utils/asserts"; | ||||
| import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   slug: "slug", | ||||
| @ -14,56 +14,54 @@ const fields = { | ||||
|   short: "short", | ||||
| } 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"]; | ||||
| 
 | ||||
| export const Keys: CollectionConfig = { | ||||
|   slug: collectionSlug(labels.plural), | ||||
|   labels, | ||||
|   typescript: { interface: labels.singular }, | ||||
|   defaultSort: fields.slug, | ||||
|   admin: { | ||||
|     useAsTitle: fields.slug, | ||||
|     defaultColumns: [fields.slug, fields.type, fields.translations], | ||||
|     group: CollectionGroup.Meta, | ||||
| export const Keys: CollectionConfig = buildCollectionConfig( | ||||
|   { | ||||
|     singular: "Key", | ||||
|     plural: "Keys", | ||||
|   }, | ||||
|   timestamps: false, | ||||
|   versions: false, | ||||
|   fields: [ | ||||
|     slugField({ name: fields.slug }), | ||||
|     { | ||||
|       name: fields.type, | ||||
|       type: "select", | ||||
|       required: true, | ||||
|       options: Object.entries(KeysTypes).map(([value, label]) => ({ label, value })), | ||||
|   () => ({ | ||||
|     defaultSort: fields.slug, | ||||
|     admin: { | ||||
|       useAsTitle: fields.slug, | ||||
|       defaultColumns: [fields.slug, fields.type, fields.translations], | ||||
|       group: CollectionGroup.Meta, | ||||
|     }, | ||||
|     localizedFields({ | ||||
|       name: fields.translations, | ||||
|       interfaceName: "CategoryTranslations", | ||||
|       admin: { | ||||
|         useAsTitle: fields.name, | ||||
|     timestamps: false, | ||||
|     versions: false, | ||||
|     fields: [ | ||||
|       slugField({ name: fields.slug }), | ||||
|       { | ||||
|         name: fields.type, | ||||
|         type: "select", | ||||
|         required: true, | ||||
|         options: Object.entries(KeysTypes).map(([value, label]) => ({ label, value })), | ||||
|       }, | ||||
|       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%", | ||||
|               }, | ||||
|             }, | ||||
|           ], | ||||
|       localizedFields({ | ||||
|         name: fields.translations, | ||||
|         interfaceName: "CategoryTranslations", | ||||
|         admin: { | ||||
|           useAsTitle: fields.name, | ||||
|         }, | ||||
|       ], | ||||
|     }), | ||||
|   ], | ||||
| }; | ||||
|         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 { collectionSlug } from "../utils/string"; | ||||
| import { buildCollectionConfig } from "../utils/collectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   id: "id", | ||||
|   name: "name", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| const labels = { | ||||
|   singular: "Language", | ||||
|   plural: "Languages", | ||||
| } as const satisfies { singular: string; plural: string }; | ||||
| 
 | ||||
| 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, | ||||
| export const Languages = buildCollectionConfig( | ||||
|   { | ||||
|     singular: "Language", | ||||
|     plural: "Languages", | ||||
|   }, | ||||
|   timestamps: false, | ||||
|   fields: [ | ||||
|     { | ||||
|       name: fields.id, | ||||
|       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...)"; | ||||
|   () => ({ | ||||
|     defaultSort: fields.name, | ||||
|     admin: { | ||||
|       useAsTitle: fields.name, | ||||
|       defaultColumns: [fields.name, fields.id], | ||||
|       group: CollectionGroup.Meta, | ||||
|     }, | ||||
|     timestamps: false, | ||||
|     fields: [ | ||||
|       { | ||||
|         name: fields.id, | ||||
|         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, | ||||
|       type: "text", | ||||
|       unique: true, | ||||
|       required: true, | ||||
|     }, | ||||
|   ], | ||||
| }; | ||||
|       { | ||||
|         name: fields.name, | ||||
|         type: "text", | ||||
|         unique: true, | ||||
|         required: true, | ||||
|       }, | ||||
|     ], | ||||
|   }) | ||||
| ); | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| import { CollectionConfig } from "payload/types"; | ||||
| import { CollectionGroup } from "../../constants"; | ||||
| import { collectionSlug } from "../../utils/string"; | ||||
| import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   filename: "filename", | ||||
| @ -8,57 +7,53 @@ const fields = { | ||||
|   filesize: "filesize", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| const labels = { | ||||
|   singular: "Library Item Thumbnail", | ||||
|   plural: "Library Item Thumbnails", | ||||
| } as const satisfies { singular: string; plural: string }; | ||||
| 
 | ||||
| export const LibraryItemThumbnails: CollectionConfig = { | ||||
|   slug: collectionSlug(labels.plural), | ||||
|   labels, | ||||
|   typescript: { interface: labels.singular }, | ||||
|   defaultSort: fields.filename, | ||||
|   admin: { | ||||
|     useAsTitle: fields.filename, | ||||
|     group: CollectionGroup.Media, | ||||
| export const LibraryItemThumbnails = buildCollectionConfig( | ||||
|   { | ||||
|     singular: "Library Item Thumbnail", | ||||
|     plural: "Library Item Thumbnails", | ||||
|   }, | ||||
| 
 | ||||
|   upload: { | ||||
|     staticDir: `../uploads/${labels.plural}`, | ||||
|     mimeTypes: ["image/*"], | ||||
|     imageSizes: [ | ||||
|       { | ||||
|         name: "og", | ||||
|         height: 1024, | ||||
|         width: 1024, | ||||
|         fit: "contain", | ||||
|         formatOptions: { | ||||
|           format: "jpg", | ||||
|           options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||
|   ({ labels }) => ({ | ||||
|     defaultSort: fields.filename, | ||||
|     admin: { | ||||
|       useAsTitle: fields.filename, | ||||
|       group: CollectionGroup.Media, | ||||
|     }, | ||||
|     upload: { | ||||
|       staticDir: `../uploads/${labels.plural}`, | ||||
|       mimeTypes: ["image/*"], | ||||
|       imageSizes: [ | ||||
|         { | ||||
|           name: "og", | ||||
|           height: 1024, | ||||
|           width: 1024, | ||||
|           fit: "contain", | ||||
|           formatOptions: { | ||||
|             format: "jpg", | ||||
|             options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         name: "medium", | ||||
|         height: 1024, | ||||
|         width: 1024, | ||||
|         fit: "contain", | ||||
|         formatOptions: { | ||||
|           format: "webp", | ||||
|           options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||
|         { | ||||
|           name: "medium", | ||||
|           height: 1024, | ||||
|           width: 1024, | ||||
|           fit: "contain", | ||||
|           formatOptions: { | ||||
|             format: "webp", | ||||
|             options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         name: "large", | ||||
|         height: 2048, | ||||
|         width: 2048, | ||||
|         fit: "contain", | ||||
|         formatOptions: { | ||||
|           format: "webp", | ||||
|           options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||
|         { | ||||
|           name: "large", | ||||
|           height: 2048, | ||||
|           width: 2048, | ||||
|           fit: "contain", | ||||
|           formatOptions: { | ||||
|             format: "webp", | ||||
|             options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
| 
 | ||||
|   fields: [], | ||||
| }; | ||||
|       ], | ||||
|     }, | ||||
|     fields: [], | ||||
|   }) | ||||
| ); | ||||
|  | ||||
| @ -1,4 +1,3 @@ | ||||
| import { CollectionConfig } from "payload/types"; | ||||
| import { | ||||
|   CollectionGroup, | ||||
|   KeysTypes, | ||||
| @ -8,12 +7,12 @@ import { | ||||
| } from "../../constants"; | ||||
| import { slugField } from "../../fields/slugField/slugField"; | ||||
| import { imageField } from "../../fields/imageField/imageField"; | ||||
| import { collectionSlug } from "../../utils/string"; | ||||
| import { isDefined, isUndefined } from "../../utils/asserts"; | ||||
| import { LibraryItemThumbnails } from "../LibraryItemThumbnails/LibraryItemThumbnails"; | ||||
| import { LibraryItem } from "../../types/collections"; | ||||
| import { Keys } from "../Keys/Keys"; | ||||
| import { Languages } from "../Languages"; | ||||
| import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   status: "status", | ||||
| @ -42,11 +41,6 @@ const fields = { | ||||
|   audioSubtype: "audioSubtype", | ||||
| } 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) => { | ||||
|   if (isDefined(value) && value <= 0) return "This value must be greater than 0"; | ||||
|   return true; | ||||
| @ -58,216 +52,220 @@ const validateRequiredSizeValue = (value?: number) => { | ||||
|   return true; | ||||
| }; | ||||
| 
 | ||||
| export const LibraryItems: CollectionConfig = { | ||||
|   slug: collectionSlug(labels.plural), | ||||
|   labels, | ||||
|   typescript: { interface: labels.singular }, | ||||
|   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}`, | ||||
| export const LibraryItems = buildVersionedCollectionConfig( | ||||
|   { | ||||
|     singular: "Library Item", | ||||
|     plural: "Library Items", | ||||
|   }, | ||||
|   timestamps: true, | ||||
|   versions: { drafts: { autosave: true } }, | ||||
|   fields: [ | ||||
|     { | ||||
|       type: "row", | ||||
|       fields: [ | ||||
|         slugField({ name: fields.slug, admin: { width: "50%" } }), | ||||
|         imageField({ | ||||
|           name: fields.thumbnail, | ||||
|           relationTo: LibraryItemThumbnails.slug, | ||||
|           admin: { width: "50%" }, | ||||
|         }), | ||||
|       ], | ||||
|   () => ({ | ||||
|     defaultSort: fields.slug, | ||||
|     admin: { | ||||
|       useAsTitle: fields.slug, | ||||
|       description: | ||||
|         "A comprehensive list of all Yokoverse’s side materials (books, novellas, artbooks, \ | ||||
| stage plays, manga, drama CDs, and comics).", | ||||
|       defaultColumns: [fields.slug, fields.thumbnail, fields.status], | ||||
|       group: CollectionGroup.Collections, | ||||
|       preview: (doc) => `https://accords-library.com/library/${doc.slug}`, | ||||
|     }, | ||||
|     { | ||||
|       type: "row", | ||||
|       fields: [ | ||||
|         { name: fields.pretitle, type: "text" }, | ||||
|         { name: fields.title, type: "text", required: true }, | ||||
|         { name: fields.subtitle, type: "text" }, | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
|       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", | ||||
|     fields: [ | ||||
|       { | ||||
|         type: "row", | ||||
|         fields: [ | ||||
|           slugField({ name: fields.slug, admin: { width: "50%" } }), | ||||
|           imageField({ | ||||
|             name: fields.thumbnail, | ||||
|             relationTo: LibraryItemThumbnails.slug, | ||||
|             admin: { width: "50%" }, | ||||
|           }), | ||||
|         ], | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       name: fields.textual, | ||||
|       type: "group", | ||||
|       admin: { | ||||
|         condition: (data: Partial<LibraryItem>) => data.itemType === LibraryItemsTypes.Textual, | ||||
|       { | ||||
|         type: "row", | ||||
|         fields: [ | ||||
|           { name: fields.pretitle, type: "text" }, | ||||
|           { name: fields.title, type: "text", required: true }, | ||||
|           { name: fields.subtitle, type: "text" }, | ||||
|         ], | ||||
|       }, | ||||
|       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%" }, | ||||
|       { | ||||
|         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.textualLanguages, | ||||
|               type: "relationship", | ||||
|               relationTo: [Languages.slug], | ||||
|               hasMany: true, | ||||
|               admin: { allowCreate: false, width: "50%" }, | ||||
|           }, | ||||
|           { | ||||
|             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%", | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|         { | ||||
|           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.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.textualPageOrder, | ||||
|               label: "Page Order", | ||||
|               type: "radio", | ||||
|               options: Object.entries(LibraryItemsTextualPageOrders).map(([value, label]) => ({ | ||||
|                 label, | ||||
|                 value, | ||||
|               })), | ||||
|               admin: { | ||||
|                 layout: "horizontal", | ||||
|                 width: "33%", | ||||
|               { | ||||
|                 name: fields.height, | ||||
|                 type: "number", | ||||
|                 validate: validateRequiredSizeValue, | ||||
|                 admin: { step: 1, width: "33%", description: "in mm." }, | ||||
|               }, | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
|       name: fields.audio, | ||||
|       type: "group", | ||||
|       admin: { | ||||
|         condition: (data: Partial<LibraryItem>) => data.itemType === LibraryItemsTypes.Audio, | ||||
|               { | ||||
|                 name: fields.thickness, | ||||
|                 type: "number", | ||||
|                 validate: validateSizeValue, | ||||
|                 admin: { step: 1, width: "33%", description: "in mm." }, | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|       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.itemType, | ||||
|         type: "radio", | ||||
|         options: Object.entries(LibraryItemsTypes).map(([value, label]) => ({ label, value })), | ||||
|         admin: { | ||||
|           layout: "horizontal", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
|       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 { collectionSlug } from "../../utils/string"; | ||||
| import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   filename: "filename", | ||||
| @ -8,45 +7,41 @@ const fields = { | ||||
|   filesize: "filesize", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| const labels = { | ||||
|   singular: "Post Thumbnail", | ||||
|   plural: "Post Thumbnails", | ||||
| } as const satisfies { singular: string; plural: string }; | ||||
| 
 | ||||
| export const PostThumbnails: CollectionConfig = { | ||||
|   slug: collectionSlug(labels.plural), | ||||
|   labels, | ||||
|   typescript: { interface: labels.singular }, | ||||
|   defaultSort: fields.filename, | ||||
|   admin: { | ||||
|     useAsTitle: fields.filename, | ||||
|     group: CollectionGroup.Media, | ||||
| export const PostThumbnails = buildCollectionConfig( | ||||
|   { | ||||
|     singular: "Post Thumbnail", | ||||
|     plural: "Post Thumbnails", | ||||
|   }, | ||||
| 
 | ||||
|   upload: { | ||||
|     staticDir: `../uploads/${labels.plural}`, | ||||
|     mimeTypes: ["image/*"], | ||||
|     imageSizes: [ | ||||
|       { | ||||
|         name: "og", | ||||
|         height: 750, | ||||
|         width: 1125, | ||||
|         formatOptions: { | ||||
|           format: "jpg", | ||||
|           options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||
|   ({ labels }) => ({ | ||||
|     defaultSort: fields.filename, | ||||
|     admin: { | ||||
|       useAsTitle: fields.filename, | ||||
|       group: CollectionGroup.Media, | ||||
|     }, | ||||
|     upload: { | ||||
|       staticDir: `../uploads/${labels.plural}`, | ||||
|       mimeTypes: ["image/*"], | ||||
|       imageSizes: [ | ||||
|         { | ||||
|           name: "og", | ||||
|           height: 750, | ||||
|           width: 1125, | ||||
|           formatOptions: { | ||||
|             format: "jpg", | ||||
|             options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         name: "medium", | ||||
|         height: 1000, | ||||
|         width: 1500, | ||||
|         formatOptions: { | ||||
|           format: "webp", | ||||
|           options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||
|         { | ||||
|           name: "medium", | ||||
|           height: 1000, | ||||
|           width: 1500, | ||||
|           formatOptions: { | ||||
|             format: "webp", | ||||
|             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 { imageField } from "../../fields/imageField/imageField"; | ||||
| import { CollectionGroup, KeysTypes } from "../../constants"; | ||||
| @ -7,8 +6,8 @@ import { localizedFields } from "../../fields/translatedFields/translatedFields" | ||||
| import { isDefined, isUndefined } from "../../utils/asserts"; | ||||
| import { removeTranslatorsForTranscripts } from "./hooks/beforeValidate"; | ||||
| import { Keys } from "../Keys/Keys"; | ||||
| import { collectionSlug } from "../../utils/string"; | ||||
| import { PostThumbnails } from "../PostThumbnails/PostThumbnails"; | ||||
| import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   slug: "slug", | ||||
| @ -26,133 +25,135 @@ const fields = { | ||||
|   proofreaders: "proofreaders", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| const labels = { | ||||
|   singular: "Post", | ||||
|   plural: "Posts", | ||||
| } as const satisfies { singular: string; plural: string }; | ||||
| 
 | ||||
| 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}`, | ||||
| export const Posts = buildVersionedCollectionConfig( | ||||
|   { | ||||
|     singular: "Post", | ||||
|     plural: "Posts", | ||||
|   }, | ||||
|   hooks: { | ||||
|     beforeValidate: [removeTranslatorsForTranscripts], | ||||
|   }, | ||||
|   timestamps: true, | ||||
|   versions: { drafts: { autosave: true } }, | ||||
|   fields: [ | ||||
|     { | ||||
|       type: "row", | ||||
|       fields: [ | ||||
|         slugField({ name: fields.slug, admin: { width: "50%" } }), | ||||
|         imageField({ | ||||
|           name: fields.thumbnail, | ||||
|           relationTo: PostThumbnails.slug, | ||||
|           admin: { width: "50%" }, | ||||
|         }), | ||||
|       ], | ||||
|   () => ({ | ||||
|     defaultSort: fields.slug, | ||||
|     admin: { | ||||
|       useAsTitle: fields.slug, | ||||
|       description: | ||||
|         "News articles written by our Recorders! Here you will find announcements about \ | ||||
| new merch/items releases, guides, theories, unboxings, showcases...", | ||||
|       defaultColumns: [fields.slug, fields.thumbnail, fields.categories], | ||||
|       group: CollectionGroup.Collections, | ||||
|       preview: (doc) => `https://accords-library.com/news/${doc.slug}`, | ||||
|     }, | ||||
|     { | ||||
|       type: "row", | ||||
|       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%" }, | ||||
|         }, | ||||
|       ], | ||||
|     hooks: { | ||||
|       beforeValidate: [removeTranslatorsForTranscripts], | ||||
|     }, | ||||
|     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) => { | ||||
|     fields: [ | ||||
|       { | ||||
|         type: "row", | ||||
|         fields: [ | ||||
|           slugField({ name: fields.slug, admin: { width: "50%" } }), | ||||
|           imageField({ | ||||
|             name: fields.thumbnail, | ||||
|             relationTo: PostThumbnails.slug, | ||||
|             admin: { width: "50%" }, | ||||
|           }), | ||||
|         ], | ||||
|       }, | ||||
|       { | ||||
|         type: "row", | ||||
|         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({ | ||||
|         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 ( | ||||
|                     isUndefined(siblingData.language) || | ||||
|                     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)) { | ||||
|                   return true; | ||||
|                 } | ||||
|                 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."; | ||||
|               { | ||||
|                 name: fields.proofreaders, | ||||
|                 type: "relationship", | ||||
|                 relationTo: Recorders.slug, | ||||
|                 hasMany: true, | ||||
|                 admin: { width: "50%" }, | ||||
|               }, | ||||
|             }, | ||||
|             { | ||||
|               name: fields.proofreaders, | ||||
|               type: "relationship", | ||||
|               relationTo: Recorders.slug, | ||||
|               hasMany: true, | ||||
|               admin: { width: "50%" }, | ||||
|             }, | ||||
|           ], | ||||
|             ], | ||||
|           }, | ||||
|           { name: fields.content, type: "richText", admin: { hideGutter: true } }, | ||||
|         ], | ||||
|       }), | ||||
|       { | ||||
|         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 } }, | ||||
|       ], | ||||
|     }), | ||||
|     { | ||||
|       name: fields.publishedDate, | ||||
|       type: "date", | ||||
|       defaultValue: new Date().toISOString(), | ||||
|       admin: { | ||||
|         date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" }, | ||||
|         position: "sidebar", | ||||
|         required: true, | ||||
|       }, | ||||
|       required: true, | ||||
|     }, | ||||
|     { | ||||
|       name: fields.hidden, | ||||
|       type: "checkbox", | ||||
|       required: false, | ||||
|       defaultValue: false, | ||||
|       admin: { | ||||
|         description: "If enabled, the post won't appear in the 'News' section", | ||||
|         position: "sidebar", | ||||
|       { | ||||
|         name: fields.hidden, | ||||
|         type: "checkbox", | ||||
|         required: false, | ||||
|         defaultValue: false, | ||||
|         admin: { | ||||
|           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: translations.map(({ translators, ...translation }) => { | ||||
|   translations: translations?.map(({ translators, ...translation }) => { | ||||
|     if (translation.language === translation.sourceLanguage) { | ||||
|       return { ...translation, translators: [] }; | ||||
|     } | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| import { CollectionConfig } from "payload/types"; | ||||
| import { CollectionGroup } from "../../constants"; | ||||
| import { collectionSlug } from "../../utils/string"; | ||||
| import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   filename: "filename", | ||||
| @ -8,46 +7,42 @@ const fields = { | ||||
|   filesize: "filesize", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| const labels = { | ||||
|   singular: "Recorder Thumbnail", | ||||
|   plural: "Recorder Thumbnails", | ||||
| } as const satisfies { singular: string; plural: string }; | ||||
| 
 | ||||
| export const RecorderThumbnails: CollectionConfig = { | ||||
|   slug: collectionSlug(labels.plural), | ||||
|   labels, | ||||
|   typescript: { interface: labels.singular }, | ||||
|   defaultSort: fields.filename, | ||||
|   admin: { | ||||
|     useAsTitle: fields.filename, | ||||
|     group: CollectionGroup.Media, | ||||
| export const RecorderThumbnails = buildCollectionConfig( | ||||
|   { | ||||
|     singular: "Recorder Thumbnail", | ||||
|     plural: "Recorder Thumbnails", | ||||
|   }, | ||||
| 
 | ||||
|   upload: { | ||||
|     staticDir: `../uploads/${labels.plural}`, | ||||
|     adminThumbnail: "small", | ||||
|     mimeTypes: ["image/*"], | ||||
|     imageSizes: [ | ||||
|       { | ||||
|         name: "og", | ||||
|         height: 256, | ||||
|         width: 256, | ||||
|         formatOptions: { | ||||
|           format: "jpg", | ||||
|           options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||
|   ({ labels }) => ({ | ||||
|     defaultSort: fields.filename, | ||||
|     admin: { | ||||
|       useAsTitle: fields.filename, | ||||
|       group: CollectionGroup.Media, | ||||
|     }, | ||||
|     upload: { | ||||
|       staticDir: `../uploads/${labels.plural}`, | ||||
|       adminThumbnail: "small", | ||||
|       mimeTypes: ["image/*"], | ||||
|       imageSizes: [ | ||||
|         { | ||||
|           name: "og", | ||||
|           height: 256, | ||||
|           width: 256, | ||||
|           formatOptions: { | ||||
|             format: "jpg", | ||||
|             options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         name: "small", | ||||
|         height: 128, | ||||
|         width: 128, | ||||
|         formatOptions: { | ||||
|           format: "webp", | ||||
|           options: { effort: 6, quality: 80, alphaQuality: 80 }, | ||||
|         { | ||||
|           name: "small", | ||||
|           height: 128, | ||||
|           width: 128, | ||||
|           formatOptions: { | ||||
|             format: "webp", | ||||
|             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 { Languages } from "../Languages"; | ||||
| import { beforeDuplicate } from "./hooks/beforeDuplicate"; | ||||
| import { CollectionGroup } from "../../constants"; | ||||
| import { collectionSlug } from "../../utils/string"; | ||||
| import { RecorderThumbnails } from "../RecorderThumbnails/RecorderThumbnails"; | ||||
| import { imageField } from "../../fields/imageField/imageField"; | ||||
| import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   username: "username", | ||||
| @ -16,79 +15,77 @@ const fields = { | ||||
|   avatar: "avatar", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| const labels = { | ||||
|   singular: "Recorder", | ||||
|   plural: "Recorders", | ||||
| } as const satisfies { singular: string; plural: string }; | ||||
| 
 | ||||
| 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, | ||||
| export const Recorders = buildCollectionConfig( | ||||
|   { | ||||
|     singular: "Recorder", | ||||
|     plural: "Recorders", | ||||
|   }, | ||||
|   timestamps: false, | ||||
|   fields: [ | ||||
|     { | ||||
|       type: "row", | ||||
|       fields: [ | ||||
|         { | ||||
|           name: fields.username, | ||||
|           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%" }, | ||||
|         }), | ||||
|   () => ({ | ||||
|     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, | ||||
|     }, | ||||
|     { | ||||
|       name: fields.languages, | ||||
|       type: "relationship", | ||||
|       relationTo: Languages.slug, | ||||
|       hasMany: true, | ||||
|       admin: { | ||||
|         allowCreate: false, | ||||
|         description: "List of language(s) that this recorder is familiar with", | ||||
|     timestamps: false, | ||||
|     fields: [ | ||||
|       { | ||||
|         type: "row", | ||||
|         fields: [ | ||||
|           { | ||||
|             name: fields.username, | ||||
|             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.biographies, | ||||
|       interfaceName: "RecorderBiographies", | ||||
|       admin: { | ||||
|         useAsTitle: fields.biography, | ||||
|         description: | ||||
|           "A short personal description about you or your involvement with this project or the franchise", | ||||
|       { | ||||
|         name: fields.languages, | ||||
|         type: "relationship", | ||||
|         relationTo: Languages.slug, | ||||
|         hasMany: true, | ||||
|         admin: { | ||||
|           allowCreate: false, | ||||
|           description: "List of language(s) that this recorder is familiar with", | ||||
|         }, | ||||
|       }, | ||||
|       fields: [{ name: fields.biography, type: "textarea" }], | ||||
|     }), | ||||
|     { | ||||
|       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", | ||||
|       localizedFields({ | ||||
|         name: fields.biographies, | ||||
|         interfaceName: "RecorderBiographies", | ||||
|         admin: { | ||||
|           useAsTitle: fields.biography, | ||||
|           description: | ||||
|             "A short personal description about you or your involvement with this project or the franchise", | ||||
|         }, | ||||
|         fields: [{ name: fields.biography, type: "textarea" }], | ||||
|       }), | ||||
|       { | ||||
|         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 } from "../constants"; | ||||
| import { collectionSlug } from "../utils/string"; | ||||
| import { CollectionGroup, UserRoles } from "../constants"; | ||||
| import { Recorders } from "./Recorders/Recorders"; | ||||
| import { buildCollectionConfig } from "../utils/collectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   recorder: "recorder", | ||||
|   name: "name", | ||||
|   email: "email", | ||||
|   role: "role", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| const labels = { | ||||
|   singular: "User", | ||||
|   plural: "Users", | ||||
| } as const satisfies { singular: string; plural: string }; | ||||
| 
 | ||||
| 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, | ||||
| export const Users = buildCollectionConfig( | ||||
|   { | ||||
|     singular: "User", | ||||
|     plural: "Users", | ||||
|   }, | ||||
|   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", | ||||
|   RightToLeft = "Right to left", | ||||
| } | ||||
| 
 | ||||
| export enum UserRoles { | ||||
|   Admin = "Admin", | ||||
|   Recorder = "Recorder", | ||||
| } | ||||
|  | ||||
| @ -1,11 +1,13 @@ | ||||
| import { Props } from "payload/components/views/Cell"; | ||||
| import { useState, useEffect } from "react"; | ||||
| import React from "react"; | ||||
| import { isUndefined } from "../../utils/asserts"; | ||||
| 
 | ||||
| export const Cell = ({ cellData, field }: Props): JSX.Element => { | ||||
|   const [imageURL, setImageURL] = useState<string>(); | ||||
|   useEffect(() => { | ||||
|     const fetchUrl = async () => { | ||||
|       if (isUndefined(cellData)) return; | ||||
|       if (typeof cellData !== "string") return; | ||||
|       if (field.type !== "upload") return; | ||||
|       const result = await (await fetch(`/api/${field.relationTo}/${cellData}`)).json(); | ||||
|  | ||||
| @ -44,10 +44,11 @@ export const localizedFields = ({ | ||||
|     components: { | ||||
|       Cell: ({ cellData }) => | ||||
|         Cell({ | ||||
|           cellData: cellData.map((row) => ({ | ||||
|             language: row.language, | ||||
|             title: isDefined(useAsTitle) ? row[useAsTitle] : undefined, | ||||
|           })), | ||||
|           cellData: | ||||
|             cellData?.map((row) => ({ | ||||
|               language: row.language, | ||||
|               title: isDefined(useAsTitle) ? row[useAsTitle] : undefined, | ||||
|             })) ?? [], | ||||
|         }), | ||||
|       RowLabel: ({ data }) => | ||||
|         RowLabel({ | ||||
|  | ||||
| @ -93,6 +93,7 @@ export interface LibraryItem { | ||||
|         }[]; | ||||
|   }; | ||||
|   releaseDate?: string; | ||||
|   lastModifiedBy: string | User; | ||||
|   updatedAt: string; | ||||
|   createdAt: string; | ||||
|   _status?: "draft" | "published"; | ||||
| @ -154,6 +155,57 @@ export interface Language { | ||||
|   id: 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 { | ||||
|   id: string; | ||||
|   slug: string; | ||||
| @ -188,6 +240,7 @@ export interface Content { | ||||
|     audio?: string | File; | ||||
|     id?: string; | ||||
|   }[]; | ||||
|   lastModifiedBy: string | User; | ||||
|   updatedAt: string; | ||||
|   createdAt: string; | ||||
|   _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 { | ||||
|   content: { | ||||
|     [k: string]: unknown; | ||||
| @ -511,6 +527,7 @@ export interface Post { | ||||
|   }[]; | ||||
|   publishedDate: string; | ||||
|   hidden?: boolean; | ||||
|   lastModifiedBy: string | User; | ||||
|   updatedAt: string; | ||||
|   createdAt: string; | ||||
|   _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 slugify from "slugify"; | ||||
| 
 | ||||
| export const shortenEllipsis = (text: string, length: number): string => | ||||
|   text.length - 3 > length ? `${text.substring(0, length)}...` : text; | ||||
| 
 | ||||
| export const formatLanguageCode = (code: string): string => | ||||
|   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