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