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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -42,3 +42,8 @@ export enum LibraryItemsTextualPageOrders {
LeftToRight = "Left to right",
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 { 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();

View File

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

View File

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

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 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 });
ISO6391.validate(code) ? ISO6391.getName(code) : code;

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