Streamlined collection config

This commit is contained in:
DrMint 2023-07-23 01:32:32 +02:00
parent 0a45ebb134
commit 65286f0c66
21 changed files with 1074 additions and 997 deletions

View File

@ -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%" },
},
],
},
],
})
);

View File

@ -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: [], );
};

View File

@ -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%" }, },
}, ],
], },
}, ],
], },
}, ],
], },
}, ],
], }),
}), ],
], })
}; );

View File

@ -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 })),
},
],
})
);

View File

@ -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%",
},
},
],
},
],
}),
],
})
);

View File

@ -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, },
}, ],
], })
}; );

View File

@ -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: [], );
};

View File

@ -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 Yokoverses 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",
},
},
],
})
);

View File

@ -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: [], );
};

View File

@ -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",
}, },
}, ],
], })
}; );

View File

@ -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: [] };
} }

View File

@ -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: [], );
};

View File

@ -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",
},
}, },
}, ],
], })
}; );

View File

@ -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%" },
},
],
},
],
})
);

View File

@ -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",
}

View File

@ -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();

View File

@ -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({

View File

@ -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;
}

View 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 },
};
};

View File

@ -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 });

View 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()],
};
};