More updates

This commit is contained in:
DrMint 2023-07-22 20:32:48 +02:00
parent 8d68ff50ce
commit b436967b3e
18 changed files with 1041 additions and 393 deletions

View File

@ -0,0 +1,65 @@
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 { Contents } from "../Contents/Contents";
const fields = {
slug: "slug",
translations: "translations",
name: "name",
subfolders: "subfolders",
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,
},
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

@ -0,0 +1,52 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroup } from "../../constants";
import { collectionSlug } from "../../utils/string";
const fields = {
filename: "filename",
mimeType: "mimeType",
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,
},
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 },
},
},
],
},
fields: [],
};

View File

@ -1,169 +1,93 @@
import { Block, BlockField } from "payload/types";
import { cueBlock } from "./cueBlock"; import { cueBlock } from "./cueBlock";
import { textBlock } from "./textBlock"; import { textBlock } from "./textBlock";
import { transcriptBlock } from "./transcriptBlock"; import { transcriptBlock } from "./transcriptBlock";
import { lineBlock } from "./lineBlock"; import { lineBlock } from "./lineBlock";
import { quoteBlock } from "./quoteBlock";
import { BlocksConfig, generateBlocks } from "../../../utils/recursiveBlocks";
const INITIAL_DEPTH = 1; const enum BlockName {
const MAX_DEPTH = 4;
enum BlockName {
Text = "Text", Text = "Text",
Section = "Section", Section = "Section",
Tabs = "Tabs", Tabs = "Tabs",
Tab = "Tab", Tab = "Tab",
Columns = "Columns",
Column = "Column",
Transcript = "Transcript", Transcript = "Transcript",
Collapsible = "Collapsible",
Accordion = "Accordion",
Line = "Line", Line = "Line",
Cue = "Cue", Cue = "Cue",
Quote = "Quote",
} }
const rootBlocksNames: BlockName[] = [ const blocksConfig: BlocksConfig<BlockName> = {
BlockName.Section, Text: {
BlockName.Collapsible, root: true,
BlockName.Columns, block: textBlock,
BlockName.Tabs, },
BlockName.Accordion, Section: {
BlockName.Text, root: true,
BlockName.Transcript, block: {
];
const recursiveBlocks: BlockName[] = [
BlockName.Section,
BlockName.Collapsible,
BlockName.Accordion,
BlockName.Tabs,
BlockName.Tab,
BlockName.Column,
BlockName.Columns,
];
const blocksChildren: Record<BlockName, BlockName[]> = {
Tabs: [BlockName.Tab],
Columns: [BlockName.Column],
Section: rootBlocksNames,
Collapsible: rootBlocksNames,
Tab: rootBlocksNames,
Column: rootBlocksNames,
Accordion: [BlockName.Collapsible],
Text: [],
Transcript: [BlockName.Line, BlockName.Cue],
Cue: [],
Line: [],
};
export type RecursiveBlock = Omit<Block, "fields"> & {
fields: Omit<BlockField, "blocks" | "type"> & {
newDepth: (currentDepth: number) => number;
blocks: BlockName[];
};
};
// TODO: Check for loops in the block graph instead of manually defining recursive blocks
const isNotRecursiveBlock = (name: BlockName): boolean => !recursiveBlocks.includes(name);
const implementationForRecursiveBlocks = (
currentDepth: number,
{ slug, interfaceName, labels, fields: { newDepth, blocks, ...fieldsProps } }: RecursiveBlock
): Block => ({
slug: [slug, currentDepth].join("_"),
interfaceName: [interfaceName, currentDepth].join("_"),
labels,
fields: [
{
...fieldsProps,
type: "blocks",
blocks: blocks
.filter((block) => {
if (currentDepth < MAX_DEPTH) return true;
if (blocks.filter(isNotRecursiveBlock).length === 0) return true;
return isNotRecursiveBlock(block);
})
.map((block) => implementations[block](newDepth(currentDepth))),
},
],
});
const implementations: Record<BlockName, (currentDepth: number) => Block> = {
Cue: () => cueBlock,
Text: () => textBlock,
Transcript: () => transcriptBlock,
Line: () => lineBlock,
Section: (currentDepth) =>
implementationForRecursiveBlocks(currentDepth, {
slug: "section", slug: "section",
interfaceName: "Section",
labels: { singular: "Section", plural: "Sections" }, labels: { singular: "Section", plural: "Sections" },
fields: { recursion: {
name: "content", name: "content",
condition: (depth) => depth < 5,
newDepth: (depth) => depth + 1, newDepth: (depth) => depth + 1,
blocks: blocksChildren.Section, blocks: [
BlockName.Section,
BlockName.Tabs,
BlockName.Transcript,
BlockName.Quote,
BlockName.Text,
],
}, },
}), },
Accordion: (currentDepth) => },
implementationForRecursiveBlocks(currentDepth, { Tabs: {
slug: "accordion", root: true,
interfaceName: "Accordion", block: {
labels: { singular: "Accordion", plural: "Accordions" },
fields: {
name: "content",
newDepth: (depth) => depth + 1,
blocks: blocksChildren.Accordion,
},
}),
Collapsible: (currentDepth) =>
implementationForRecursiveBlocks(currentDepth, {
slug: "collapsible",
interfaceName: "Collapsible",
labels: { singular: "Collapsible", plural: "Collapsibles" },
fields: {
name: "content",
newDepth: (depth) => depth + 1,
blocks: blocksChildren.Collapsible,
},
}),
Tabs: (currentDepth) =>
implementationForRecursiveBlocks(currentDepth, {
slug: "tabs", slug: "tabs",
interfaceName: "Tabs",
labels: { singular: "Tabs", plural: "Tabs" }, labels: { singular: "Tabs", plural: "Tabs" },
fields: { name: "tabs", newDepth: (depth) => depth, blocks: blocksChildren.Tabs }, recursion: {
}), name: "tabs",
Tab: (currentDepth) =>
implementationForRecursiveBlocks(currentDepth, {
slug: "tab",
interfaceName: "Tab",
labels: { singular: "Tab", plural: "Tabs" },
fields: {
name: "content",
newDepth: (depth) => depth + 1,
blocks: blocksChildren.Tab,
},
}),
Columns: (currentDepth) =>
implementationForRecursiveBlocks(currentDepth, {
slug: "columns",
interfaceName: "Columns",
labels: { singular: "Columns", plural: "Columns" },
fields: {
name: "columns",
newDepth: (depth) => depth, newDepth: (depth) => depth,
blocks: blocksChildren.Columns, condition: (depth, parents) => !parents.includes(BlockName.Tabs) && depth < 5,
blocks: [BlockName.Tab],
}, },
}), },
Column: (currentDepth) => },
implementationForRecursiveBlocks(currentDepth, { Tab: {
slug: "column", root: false,
interfaceName: "Column", block: {
labels: { singular: "Column", plural: "Columns" }, slug: "tab",
fields: { name: "content", newDepth: (depth) => depth + 1, blocks: blocksChildren.Column }, labels: { singular: "Tab", plural: "Tabs" },
}), recursion: {
name: "content",
condition: (depth) => depth < 5,
newDepth: (depth) => depth + 1,
blocks: [
BlockName.Section,
BlockName.Tabs,
BlockName.Transcript,
BlockName.Quote,
BlockName.Text,
],
},
},
},
Transcript: {
root: true,
block: transcriptBlock,
},
Cue: {
root: false,
block: cueBlock,
},
Line: {
root: false,
block: lineBlock,
},
Quote: {
root: true,
block: quoteBlock,
},
}; };
export const rootBlocks: Block[] = rootBlocksNames export const contentBlocks = generateBlocks(blocksConfig);
.filter((block) => block in implementations)
.map((block) => implementations[block](INITIAL_DEPTH));

View File

@ -0,0 +1,24 @@
import { Block } from "payload/types";
export const quoteBlock: Block = {
slug: "quoteBlock",
interfaceName: "QuoteBlock",
labels: { singular: "Quote", plural: "Quotes" },
fields: [
{
name: "from",
type: "text",
required: true,
},
{
name: "content",
type: "richText",
label: false,
required: true,
admin: {
hideGutter: true,
elements: [],
},
},
],
};

View File

@ -1,14 +1,15 @@
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from "payload/types";
import { collectionSlug } from "../../utils/string"; import { collectionSlug } from "../../utils/string";
import { CollectionGroup, FileTypes, TagsTypes } 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";
import { Tags } from "../Tags/Tags"; import { Keys } from "../Keys/Keys";
import { localizedFields } from "../../fields/translatedFields/translatedFields"; import { localizedFields } from "../../fields/translatedFields/translatedFields";
import { Recorders } from "../Recorders/Recorders"; import { Recorders } from "../Recorders/Recorders";
import { isDefined } from "../../utils/asserts"; import { isDefined } from "../../utils/asserts";
import { fileField } from "../../fields/fileField/fileField"; import { fileField } from "../../fields/fileField/fileField";
import { rootBlocks } from "./Blocks/blocks"; import { contentBlocks } from "./Blocks/blocks";
import { ContentThumbnails } from "../ContentThumbnails/ContentThumbnails";
const fields = { const fields = {
slug: "slug", slug: "slug",
@ -29,6 +30,7 @@ const fields = {
videoNotes: "videoNotes", videoNotes: "videoNotes",
audio: "audio", audio: "audio",
audioNotes: "videoNotes", audioNotes: "videoNotes",
status: "status",
} as const satisfies Record<string, string>; } as const satisfies Record<string, string>;
const labels = { const labels = {
@ -43,42 +45,47 @@ export const Contents: CollectionConfig = {
defaultSort: fields.slug, defaultSort: fields.slug,
admin: { admin: {
useAsTitle: fields.slug, useAsTitle: fields.slug,
defaultColumns: [fields.slug, fields.thumbnail, fields.categories], defaultColumns: [
fields.slug,
fields.thumbnail,
fields.categories,
fields.type,
fields.translations,
fields.status,
],
group: CollectionGroup.Collections, group: CollectionGroup.Collections,
preview: (doc) => `https://accords-library.com/contents/${doc.slug}`, preview: (doc) => `https://accords-library.com/contents/${doc.slug}`,
}, },
timestamps: true, timestamps: true,
versions: { drafts: true }, versions: { drafts: { autosave: true } },
fields: [ fields: [
{ {
type: "row", type: "row",
fields: [ fields: [
slugField({ name: fields.slug, admin: { width: "50%" } }), slugField({ name: fields.slug, admin: { width: "50%" } }),
imageField({ name: fields.thumbnail, admin: { width: "50%" } }), imageField({
name: fields.thumbnail,
relationTo: ContentThumbnails.slug,
admin: { width: "50%" },
}),
], ],
}, },
{
name: "testing",
type: "blocks",
admin: { initCollapsed: true },
blocks: rootBlocks,
},
{ {
type: "row", type: "row",
fields: [ fields: [
{ {
name: fields.categories, name: fields.categories,
type: "relationship", type: "relationship",
relationTo: [Tags.slug], relationTo: [Keys.slug],
filterOptions: { type: { equals: TagsTypes.Categories } }, filterOptions: { type: { equals: KeysTypes.Categories } },
hasMany: true, hasMany: true,
admin: { allowCreate: false, width: "50%" }, admin: { allowCreate: false, width: "50%" },
}, },
{ {
name: fields.type, name: fields.type,
type: "relationship", type: "relationship",
relationTo: [Tags.slug], relationTo: [Keys.slug],
filterOptions: { type: { equals: TagsTypes.Contents } }, filterOptions: { type: { equals: KeysTypes.Contents } },
admin: { allowCreate: false, width: "50%" }, admin: { allowCreate: false, width: "50%" },
}, },
], ],
@ -148,8 +155,10 @@ export const Contents: CollectionConfig = {
{ {
name: fields.textContent, name: fields.textContent,
label: "Content", label: "Content",
type: "richText", labels: { singular: "Block", plural: "Blocks" },
admin: { hideGutter: true }, type: "blocks",
admin: { initCollapsed: true },
blocks: contentBlocks,
}, },
{ {
name: fields.textNotes, name: fields.textNotes,

View File

@ -1,22 +1,27 @@
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from "payload/types";
import { slugField } from "../../fields/slugField/slugField"; import { slugField } from "../../fields/slugField/slugField";
import { CollectionGroup, TagsTypes } 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 { collectionSlug } from "../../utils/string";
import { Key } from "../../types/collections";
import { isDefined } from "../../utils/asserts";
const fields = { const fields = {
slug: "slug", slug: "slug",
translations: "translations", translations: "translations",
type: "type", type: "type",
name: "name", name: "name",
short: "short",
} as const satisfies Record<string, string>; } as const satisfies Record<string, string>;
const labels = { const labels = {
singular: "Tag", singular: "Key",
plural: "Tags", plural: "Keys",
} as const satisfies { singular: string; plural: string }; } as const satisfies { singular: string; plural: string };
export const Tags: CollectionConfig = { const keysTypesWithShort: (keyof typeof KeysTypes)[] = ["Categories", "GamePlatforms"];
export const Keys: CollectionConfig = {
slug: collectionSlug(labels.plural), slug: collectionSlug(labels.plural),
labels, labels,
typescript: { interface: labels.singular }, typescript: { interface: labels.singular },
@ -34,7 +39,7 @@ export const Tags: CollectionConfig = {
name: fields.type, name: fields.type,
type: "select", type: "select",
required: true, required: true,
options: Object.entries(TagsTypes).map(([value, label]) => ({ label, value })), options: Object.entries(KeysTypes).map(([value, label]) => ({ label, value })),
}, },
localizedFields({ localizedFields({
name: fields.translations, name: fields.translations,
@ -42,7 +47,23 @@ export const Tags: CollectionConfig = {
admin: { admin: {
useAsTitle: fields.name, useAsTitle: fields.name,
}, },
fields: [{ name: fields.name, type: "text", required: true }], 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

@ -0,0 +1,64 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroup } from "../../constants";
import { collectionSlug } from "../../utils/string";
const fields = {
filename: "filename",
mimeType: "mimeType",
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,
},
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: "large",
height: 2048,
width: 2048,
fit: "contain",
formatOptions: {
format: "webp",
options: { effort: 6, quality: 80, alphaQuality: 80 },
},
},
],
},
fields: [],
};

View File

@ -1,9 +1,19 @@
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from "payload/types";
import { CollectionGroup } from "../../constants"; import {
CollectionGroup,
KeysTypes,
LibraryItemsTextualBindingTypes,
LibraryItemsTextualPageOrders,
LibraryItemsTypes,
} 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 { collectionSlug } from "../../utils/string";
import { isDefined, isUndefined } from "../../utils/asserts"; 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";
const fields = { const fields = {
status: "status", status: "status",
@ -20,6 +30,16 @@ const fields = {
width: "width", width: "width",
height: "height", height: "height",
thickness: "thickness", thickness: "thickness",
releaseDate: "releaseDate",
itemType: "itemType",
textual: "textual",
textualSubtype: "subtype",
textualBindingType: "bindingType",
textualPageCount: "pageCount",
textualPageOrder: "pageOrder",
textualLanguages: "languages",
audio: "audio",
audioSubtype: "audioSubtype",
} as const satisfies Record<string, string>; } as const satisfies Record<string, string>;
const labels = { const labels = {
@ -50,13 +70,17 @@ export const LibraryItems: CollectionConfig = {
preview: (doc) => `https://accords-library.com/library/${doc.slug}`, preview: (doc) => `https://accords-library.com/library/${doc.slug}`,
}, },
timestamps: true, timestamps: true,
versions: { drafts: true }, versions: { drafts: { autosave: true } },
fields: [ fields: [
{ {
type: "row", type: "row",
fields: [ fields: [
slugField({ name: fields.slug, admin: { width: "50%" } }), slugField({ name: fields.slug, admin: { width: "50%" } }),
imageField({ name: fields.thumbnail, admin: { width: "50%" } }), imageField({
name: fields.thumbnail,
relationTo: LibraryItemThumbnails.slug,
admin: { width: "50%" },
}),
], ],
}, },
{ {
@ -144,5 +168,106 @@ export const LibraryItems: CollectionConfig = {
}, },
], ],
}, },
{
name: fields.itemType,
type: "radio",
options: Object.entries(LibraryItemsTypes).map(([value, label]) => ({ label, value })),
admin: {
layout: "horizontal",
},
},
{
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

@ -6,15 +6,14 @@ const fields = {
filename: "filename", filename: "filename",
mimeType: "mimeType", mimeType: "mimeType",
filesize: "filesize", filesize: "filesize",
alt: "alt",
} as const satisfies Record<string, string>; } as const satisfies Record<string, string>;
const labels = { const labels = {
singular: "Image", singular: "Post Thumbnail",
plural: "Images", plural: "Post Thumbnails",
} as const satisfies { singular: string; plural: string }; } as const satisfies { singular: string; plural: string };
export const Images: CollectionConfig = { export const PostThumbnails: CollectionConfig = {
slug: collectionSlug(labels.plural), slug: collectionSlug(labels.plural),
labels, labels,
typescript: { interface: labels.singular }, typescript: { interface: labels.singular },
@ -27,13 +26,27 @@ export const Images: CollectionConfig = {
upload: { upload: {
staticDir: `../uploads/${labels.plural}`, staticDir: `../uploads/${labels.plural}`,
mimeTypes: ["image/*"], 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 },
},
},
],
}, },
fields: [ fields: [],
{
name: fields.alt,
label: "Alt Text",
type: "text",
},
],
}; };

View File

@ -1,16 +1,18 @@
import { CollectionConfig } from "payload/types"; 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, TagsTypes } from "../../constants"; import { CollectionGroup, KeysTypes } from "../../constants";
import { Recorders } from "../Recorders/Recorders"; import { Recorders } from "../Recorders/Recorders";
import { localizedFields } from "../../fields/translatedFields/translatedFields"; 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 { Tags } from "../Tags/Tags"; import { Keys } from "../Keys/Keys";
import { collectionSlug } from "../../utils/string"; import { collectionSlug } from "../../utils/string";
import { PostThumbnails } from "../PostThumbnails/PostThumbnails";
const fields = { const fields = {
slug: "slug", slug: "slug",
hidden: "hidden",
thumbnail: "thumbnail", thumbnail: "thumbnail",
categories: "categories", categories: "categories",
authors: "authors", authors: "authors",
@ -44,13 +46,17 @@ export const Posts: CollectionConfig = {
beforeValidate: [removeTranslatorsForTranscripts], beforeValidate: [removeTranslatorsForTranscripts],
}, },
timestamps: true, timestamps: true,
versions: { drafts: true }, versions: { drafts: { autosave: true } },
fields: [ fields: [
{ {
type: "row", type: "row",
fields: [ fields: [
slugField({ name: fields.slug, admin: { width: "50%" } }), slugField({ name: fields.slug, admin: { width: "50%" } }),
imageField({ name: fields.thumbnail, admin: { width: "50%" } }), imageField({
name: fields.thumbnail,
relationTo: PostThumbnails.slug,
admin: { width: "50%" },
}),
], ],
}, },
{ {
@ -63,15 +69,15 @@ export const Posts: CollectionConfig = {
required: true, required: true,
minRows: 1, minRows: 1,
hasMany: true, hasMany: true,
admin: { width: "50%" }, admin: { width: "35%" },
}, },
{ {
name: fields.categories, name: fields.categories,
type: "relationship", type: "relationship",
relationTo: [Tags.slug], relationTo: [Keys.slug],
filterOptions: { type: { equals: TagsTypes.Categories } }, filterOptions: { type: { equals: KeysTypes.Categories } },
hasMany: true, hasMany: true,
admin: { allowCreate: false, width: "50%" }, admin: { allowCreate: false, width: "35%" },
}, },
], ],
}, },
@ -138,5 +144,15 @@ export const Posts: CollectionConfig = {
}, },
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",
},
},
], ],
}; };

View File

@ -0,0 +1,53 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroup } from "../../constants";
import { collectionSlug } from "../../utils/string";
const fields = {
filename: "filename",
mimeType: "mimeType",
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,
},
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 },
},
},
],
},
fields: [],
};

View File

@ -1,12 +1,11 @@
import { CollectionConfig } from "payload/types"; 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 { Images } from "../Images/Images";
import { Cell } from "../../fields/imageField/Cell";
import { beforeDuplicate } from "./hooks/beforeDuplicate"; import { beforeDuplicate } from "./hooks/beforeDuplicate";
import { imageField } from "../../fields/imageField/imageField";
import { CollectionGroup } from "../../constants"; import { CollectionGroup } from "../../constants";
import { collectionSlug } from "../../utils/string"; import { collectionSlug } from "../../utils/string";
import { RecorderThumbnails } from "../RecorderThumbnails/RecorderThumbnails";
import { imageField } from "../../fields/imageField/imageField";
const fields = { const fields = {
username: "username", username: "username",
@ -32,7 +31,13 @@ export const Recorders: CollectionConfig = {
hooks: { beforeDuplicate }, hooks: { beforeDuplicate },
description: description:
"Recorders are contributors of the Accord's Library project. Create a Recorder here to be able to credit them in other collections", "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.anonymize, fields.biographies, fields.languages], defaultColumns: [
fields.username,
fields.avatar,
fields.anonymize,
fields.biographies,
fields.languages,
],
group: CollectionGroup.Meta, group: CollectionGroup.Meta,
}, },
timestamps: false, timestamps: false,
@ -40,25 +45,18 @@ export const Recorders: CollectionConfig = {
{ {
type: "row", type: "row",
fields: [ fields: [
imageField({ name: fields.avatar }),
{ {
name: fields.username, name: fields.username,
type: "text", type: "text",
unique: true, unique: true,
required: true, required: true,
admin: { description: "The username must be unique" }, admin: { description: "The username must be unique", width: "33%" },
},
{
name: fields.anonymize,
type: "checkbox",
required: true,
defaultValue: false,
admin: {
width: "50%",
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",
},
}, },
imageField({
name: fields.avatar,
relationTo: RecorderThumbnails.slug,
admin: { width: "66%" },
}),
], ],
}, },
{ {
@ -81,5 +79,16 @@ export const Recorders: CollectionConfig = {
}, },
fields: [{ name: fields.biography, type: "textarea" }], 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

@ -5,7 +5,7 @@ export enum CollectionGroup {
Administration = "Administration", Administration = "Administration",
} }
export enum TagsTypes { export enum KeysTypes {
Contents = "Contents", Contents = "Contents",
LibraryAudio = "Library / Audio", LibraryAudio = "Library / Audio",
LibraryVideo = "Library / Video", LibraryVideo = "Library / Video",
@ -15,6 +15,7 @@ export enum TagsTypes {
Weapons = "Weapons", Weapons = "Weapons",
GamePlatforms = "Game Platforms", GamePlatforms = "Game Platforms",
Categories = "Categories", Categories = "Categories",
Wordings = "Wordings",
} }
export enum FileTypes { export enum FileTypes {
@ -23,3 +24,21 @@ export enum FileTypes {
ContentVideo = "Content / Video", ContentVideo = "Content / Video",
ContentAudio = "Content / Audio", ContentAudio = "Content / Audio",
} }
export enum LibraryItemsTypes {
Textual = "Textual",
Audio = "Audio",
Video = "Video",
Game = "Game",
Other = "Other",
}
export enum LibraryItemsTextualBindingTypes {
Paperback = "Paperback",
Hardcover = "Hardcover",
}
export enum LibraryItemsTextualPageOrders {
LeftToRight = "Left to right",
RightToLeft = "Right to left",
}

View File

@ -1,13 +1,11 @@
import { UploadField } from "payload/types"; import { UploadField } from "payload/types";
import { Images } from "../../collections/Images/Images";
import { Cell } from "./Cell"; import { Cell } from "./Cell";
type Props = Omit<UploadField, "type" | "relationTo">; type Props = Omit<UploadField, "type">;
export const imageField = ({ admin, ...otherProps }: Props): UploadField => ({ export const imageField = ({ admin, ...otherProps }: Props): UploadField => ({
...otherProps, ...otherProps,
type: "upload", type: "upload",
relationTo: Images.slug,
admin: { admin: {
components: { components: {
Cell, Cell,

View File

@ -3,12 +3,16 @@ import path from "path";
import { Users } from "./collections/Users"; import { Users } from "./collections/Users";
import { Languages } from "./collections/Languages"; import { Languages } from "./collections/Languages";
import { Recorders } from "./collections/Recorders/Recorders"; import { Recorders } from "./collections/Recorders/Recorders";
import { Images } from "./collections/Images/Images";
import { Posts } from "./collections/Posts/Posts"; import { Posts } from "./collections/Posts/Posts";
import { Tags } from "./collections/Tags/Tags"; import { Keys } from "./collections/Keys/Keys";
import { LibraryItems } from "./collections/LibraryItems/LibraryItems"; import { LibraryItems } from "./collections/LibraryItems/LibraryItems";
import { Contents } from "./collections/Contents/Contents"; import { Contents } from "./collections/Contents/Contents";
import { Files } from "./collections/Files/Files"; import { Files } from "./collections/Files/Files";
import { RecorderThumbnails } from "./collections/RecorderThumbnails/RecorderThumbnails";
import { PostThumbnails } from "./collections/PostThumbnails/PostThumbnails";
import { LibraryItemThumbnails } from "./collections/LibraryItemThumbnails/LibraryItemThumbnails";
import { ContentThumbnails } from "./collections/ContentThumbnails/ContentThumbnails";
import { ContentFolders } from "./collections/ContentFolders/ContentFolders";
import { Logo } from "./components/Logo"; import { Logo } from "./components/Logo";
import { Icon } from "./components/Icon"; import { Icon } from "./components/Icon";
@ -21,15 +25,30 @@ export default buildConfig({
favicon: "/public/favicon.ico", favicon: "/public/favicon.ico",
ogImage: "og.jpg", ogImage: "og.jpg",
titleSuffix: "- Accords Library", titleSuffix: "- Accords Library",
}, },
css: path.resolve(__dirname, "styles.scss"), css: path.resolve(__dirname, "styles.scss"),
}, },
collections: [
LibraryItems,
Contents,
ContentFolders,
Posts,
ContentThumbnails,
LibraryItemThumbnails,
RecorderThumbnails,
PostThumbnails,
Files,
Languages,
Recorders,
Keys,
Users,
],
globals: [], globals: [],
telemetry: false, telemetry: false,
typescript: { typescript: {
outputFile: path.resolve(__dirname, "types/collections.ts"), outputFile: path.resolve(__dirname, "types/collections.ts"),
}, },
graphQL: { graphQL: {
schemaOutputFile: path.resolve(__dirname, "generated-schema.graphql"), disable: true,
}, },
}); });

View File

@ -9,6 +9,7 @@
export type CategoryTranslations = { export type CategoryTranslations = {
language: string | Language; language: string | Language;
name: string; name: string;
short?: string;
id?: string; id?: string;
}[]; }[];
export type RecorderBiographies = { export type RecorderBiographies = {
@ -16,25 +17,39 @@ export type RecorderBiographies = {
biography?: string; biography?: string;
id?: string; id?: string;
}[]; }[];
export type ContentFoldersTranslation = {
language: string | Language;
name: string;
id?: string;
}[];
export interface Config { export interface Config {
collections: { collections: {
'library-items': LibraryItem; 'library-items': LibraryItem;
contents: Content; contents: Content;
'content-folders': ContentFolder;
posts: Post; posts: Post;
images: Image; 'content-thumbnails': ContentThumbnail;
'library-item-thumbnails': LibraryItemThumbnail;
'recorder-thumbnails': RecorderThumbnail;
'post-thumbnails': PostThumbnail;
files: File; files: File;
languages: Language; languages: Language;
recorders: Recorder; recorders: Recorder;
tags: Tag; keys: Key;
users: User; users: User;
}; };
globals: {}; globals: {};
} }
export interface LibraryItem { export interface LibraryItem {
id: string; id: string;
/**
* @minItems 2
* @maxItems 2
*/
test?: [number, number];
slug: string; slug: string;
thumbnail?: string | Image; thumbnail?: string | LibraryItemThumbnail;
pretitle?: string; pretitle?: string;
title: string; title: string;
subtitle?: string; subtitle?: string;
@ -47,13 +62,37 @@ export interface LibraryItem {
height?: number; height?: number;
thickness?: number; thickness?: number;
}; };
itemType?: 'Textual' | 'Audio' | 'Video' | 'Game' | 'Other';
textual?: {
subtype?:
| {
value: string;
relationTo: 'keys';
}[]
| {
value: Key;
relationTo: 'keys';
}[];
languages?:
| {
value: string;
relationTo: 'languages';
}[]
| {
value: Language;
relationTo: 'languages';
}[];
pageCount?: number;
bindingType?: 'Paperback' | 'Hardcover';
pageOrder?: 'LeftToRight' | 'RightToLeft';
};
releaseDate?: string;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: 'draft' | 'published'; _status?: 'draft' | 'published';
} }
export interface Image { export interface LibraryItemThumbnail {
id: string; id: string;
alt?: string;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
url?: string; url?: string;
@ -62,24 +101,69 @@ export interface Image {
filesize?: number; filesize?: number;
width?: number; width?: number;
height?: number; height?: number;
sizes?: {
og?: {
url?: string;
width?: number;
height?: number;
mimeType?: string;
filesize?: number;
filename?: string;
};
medium?: {
url?: string;
width?: number;
height?: number;
mimeType?: string;
filesize?: number;
filename?: string;
};
large?: {
url?: string;
width?: number;
height?: number;
mimeType?: string;
filesize?: number;
filename?: string;
};
};
}
export interface Key {
id: string;
slug: string;
type:
| 'Contents'
| 'LibraryAudio'
| 'LibraryVideo'
| 'LibraryTextual'
| 'LibraryGroup'
| 'Library'
| 'Weapons'
| 'GamePlatforms'
| 'Categories'
| 'Wordings';
translations?: CategoryTranslations;
}
export interface Language {
id: string;
name: string;
} }
export interface Content { export interface Content {
id: string; id: string;
slug: string; slug: string;
thumbnail?: string | Image; thumbnail?: string | ContentThumbnail;
testing?: (Section_1 | Collapsible_1 | Columns_1 | Tabs_1 | Accordion_1 | TextBlock | TranscriptBlock)[];
categories?: categories?:
| { | {
value: string; value: string;
relationTo: 'tags'; relationTo: 'keys';
}[] }[]
| { | {
value: Tag; value: Key;
relationTo: 'tags'; relationTo: 'keys';
}[]; }[];
type?: { type?: {
value: string | Tag; value: string | Key;
relationTo: 'tags'; relationTo: 'keys';
}; };
translations: { translations: {
language: string | Language; language: string | Language;
@ -91,9 +175,7 @@ export interface Content {
textTranscribers?: string[] | Recorder[]; textTranscribers?: string[] | Recorder[];
textTranslators?: string[] | Recorder[]; textTranslators?: string[] | Recorder[];
textProofreaders?: string[] | Recorder[]; textProofreaders?: string[] | Recorder[];
textContent?: { textContent?: (TextBlock | Section | Tabs | TranscriptBlock | QuoteBlock)[];
[k: string]: unknown;
}[];
textNotes?: string; textNotes?: string;
video?: string | File; video?: string | File;
videoNotes?: string; videoNotes?: string;
@ -104,29 +186,71 @@ export interface Content {
createdAt: string; createdAt: string;
_status?: 'draft' | 'published'; _status?: 'draft' | 'published';
} }
export interface Section_1 { export interface ContentThumbnail {
content?: (Section_2 | Collapsible_2 | Columns_2 | Tabs_2 | Accordion_2 | TextBlock | TranscriptBlock)[]; id: string;
id?: string; updatedAt: string;
blockName?: string; createdAt: string;
blockType: 'section_1'; 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;
};
medium?: {
url?: string;
width?: number;
height?: number;
mimeType?: string;
filesize?: number;
filename?: string;
};
};
} }
export interface Section_2 { export interface Recorder {
content?: (Section_3 | Collapsible_3 | Columns_3 | Tabs_3 | Accordion_3 | TextBlock | TranscriptBlock)[]; id: string;
id?: string; username: string;
blockName?: string; avatar?: string | RecorderThumbnail;
blockType: 'section_2'; languages?: string[] | Language[];
biographies?: RecorderBiographies;
anonymize: boolean;
} }
export interface Section_3 { export interface RecorderThumbnail {
content?: (Section_4 | Collapsible_4 | Columns_4 | Tabs_4 | Accordion_4 | TextBlock | TranscriptBlock)[]; id: string;
id?: string; updatedAt: string;
blockName?: string; createdAt: string;
blockType: 'section_3'; url?: string;
} filename?: string;
export interface Section_4 { mimeType?: string;
content?: (TextBlock | TranscriptBlock)[]; filesize?: number;
id?: string; width?: number;
blockName?: string; height?: number;
blockType: 'section_4'; 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: {
@ -136,6 +260,42 @@ export interface TextBlock {
blockName?: string; blockName?: string;
blockType: 'textBlock'; blockType: 'textBlock';
} }
export interface Section {
content?: (Section_Section | Section_Tabs | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: 'section';
}
export interface Section_Section {
content?: (Section_Section_Section | Section_Section_Tabs | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: 'section';
}
export interface Section_Section_Section {
content?: (
| Section_Section_Section_Section
| Section_Section_Section_Tabs
| TranscriptBlock
| QuoteBlock
| TextBlock
)[];
id?: string;
blockName?: string;
blockType: 'section';
}
export interface Section_Section_Section_Section {
content?: (Section_Section_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: 'section';
}
export interface Section_Section_Section_Section_Section {
content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: 'section';
}
export interface TranscriptBlock { export interface TranscriptBlock {
lines: (LineBlock | CueBlock)[]; lines: (LineBlock | CueBlock)[];
id?: string; id?: string;
@ -156,182 +316,122 @@ export interface CueBlock {
blockName?: string; blockName?: string;
blockType: 'cueBlock'; blockType: 'cueBlock';
} }
export interface Collapsible_4 { export interface QuoteBlock {
content?: (TextBlock | TranscriptBlock)[]; from: string;
content: {
[k: string]: unknown;
}[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'collapsible_4'; blockType: 'quoteBlock';
} }
export interface Columns_4 { export interface Section_Section_Section_Tabs {
columns?: Column_4[]; tabs?: Section_Section_Section_Tabs_Tab[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'columns_4'; blockType: 'tabs';
} }
export interface Column_4 { export interface Section_Section_Section_Tabs_Tab {
content?: (TextBlock | TranscriptBlock)[]; content?: (Section_Section_Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'column_4'; blockType: 'tab';
} }
export interface Tabs_4 { export interface Section_Section_Section_Tabs_Tab_Section {
tabs?: Tab_4[]; content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'tabs_4'; blockType: 'section';
} }
export interface Tab_4 { export interface Section_Section_Tabs {
content?: (TextBlock | TranscriptBlock)[]; tabs?: Section_Section_Tabs_Tab[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'tab_4'; blockType: 'tabs';
} }
export interface Accordion_4 { export interface Section_Section_Tabs_Tab {
content?: Collapsible_5[]; content?: (Section_Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'accordion_4'; blockType: 'tab';
} }
export interface Collapsible_5 { export interface Section_Section_Tabs_Tab_Section {
content?: (TextBlock | TranscriptBlock)[]; content?: (Section_Section_Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'collapsible_5'; blockType: 'section';
} }
export interface Collapsible_3 { export interface Section_Section_Tabs_Tab_Section_Section {
content?: (Section_4 | Collapsible_4 | Columns_4 | Tabs_4 | Accordion_4 | TextBlock | TranscriptBlock)[]; content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'collapsible_3'; blockType: 'section';
} }
export interface Columns_3 { export interface Section_Tabs {
columns?: Column_3[]; tabs?: Section_Tabs_Tab[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'columns_3'; blockType: 'tabs';
} }
export interface Column_3 { export interface Section_Tabs_Tab {
content?: (Section_4 | Collapsible_4 | Columns_4 | Tabs_4 | Accordion_4 | TextBlock | TranscriptBlock)[]; content?: (Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'column_3'; blockType: 'tab';
} }
export interface Tabs_3 { export interface Section_Tabs_Tab_Section {
tabs?: Tab_3[]; content?: (Section_Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'tabs_3'; blockType: 'section';
} }
export interface Tab_3 { export interface Section_Tabs_Tab_Section_Section {
content?: (Section_4 | Collapsible_4 | Columns_4 | Tabs_4 | Accordion_4 | TextBlock | TranscriptBlock)[]; content?: (Section_Tabs_Tab_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'tab_3'; blockType: 'section';
} }
export interface Accordion_3 { export interface Section_Tabs_Tab_Section_Section_Section {
content?: Collapsible_4[]; content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'accordion_3'; blockType: 'section';
} }
export interface Collapsible_2 { export interface Tabs {
content?: (Section_3 | Collapsible_3 | Columns_3 | Tabs_3 | Accordion_3 | TextBlock | TranscriptBlock)[]; tabs?: Tabs_Tab[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'collapsible_2'; blockType: 'tabs';
} }
export interface Columns_2 { export interface Tabs_Tab {
columns?: Column_2[]; content?: (Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'columns_2'; blockType: 'tab';
} }
export interface Column_2 { export interface Tabs_Tab_Section {
content?: (Section_3 | Collapsible_3 | Columns_3 | Tabs_3 | Accordion_3 | TextBlock | TranscriptBlock)[]; content?: (Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'column_2'; blockType: 'section';
} }
export interface Tabs_2 { export interface Tabs_Tab_Section_Section {
tabs?: Tab_2[]; content?: (Tabs_Tab_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'tabs_2'; blockType: 'section';
} }
export interface Tab_2 { export interface Tabs_Tab_Section_Section_Section {
content?: (Section_3 | Collapsible_3 | Columns_3 | Tabs_3 | Accordion_3 | TextBlock | TranscriptBlock)[]; content?: (Tabs_Tab_Section_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'tab_2'; blockType: 'section';
} }
export interface Accordion_2 { export interface Tabs_Tab_Section_Section_Section_Section {
content?: Collapsible_3[]; content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: 'accordion_2'; blockType: 'section';
}
export interface Collapsible_1 {
content?: (Section_2 | Collapsible_2 | Columns_2 | Tabs_2 | Accordion_2 | TextBlock | TranscriptBlock)[];
id?: string;
blockName?: string;
blockType: 'collapsible_1';
}
export interface Columns_1 {
columns?: Column_1[];
id?: string;
blockName?: string;
blockType: 'columns_1';
}
export interface Column_1 {
content?: (Section_2 | Collapsible_2 | Columns_2 | Tabs_2 | Accordion_2 | TextBlock | TranscriptBlock)[];
id?: string;
blockName?: string;
blockType: 'column_1';
}
export interface Tabs_1 {
tabs?: Tab_1[];
id?: string;
blockName?: string;
blockType: 'tabs_1';
}
export interface Tab_1 {
content?: (Section_2 | Collapsible_2 | Columns_2 | Tabs_2 | Accordion_2 | TextBlock | TranscriptBlock)[];
id?: string;
blockName?: string;
blockType: 'tab_1';
}
export interface Accordion_1 {
content?: Collapsible_2[];
id?: string;
blockName?: string;
blockType: 'accordion_1';
}
export interface Tag {
id: string;
slug: string;
type:
| 'Contents'
| 'LibraryAudio'
| 'LibraryVideo'
| 'LibraryTextual'
| 'LibraryGroup'
| 'Library'
| 'Weapons'
| 'GamePlatforms'
| 'Categories';
translations?: CategoryTranslations;
}
export interface Language {
id: string;
name: string;
}
export interface Recorder {
id: string;
avatar?: string | Image;
username: string;
anonymize: boolean;
languages?: string[] | Language[];
biographies?: RecorderBiographies;
} }
export interface File { export interface File {
id: string; id: string;
@ -340,10 +440,33 @@ export interface File {
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
export interface ContentFolder {
id: string;
slug: string;
translations?: ContentFoldersTranslation;
subfolders?:
| {
value: string;
relationTo: 'content-folders';
}[]
| {
value: ContentFolder;
relationTo: 'content-folders';
}[];
contents?:
| {
value: string;
relationTo: 'contents';
}[]
| {
value: Content;
relationTo: 'contents';
}[];
}
export interface Post { export interface Post {
id: string; id: string;
slug: string; slug: string;
thumbnail?: string | Image; thumbnail?: string | PostThumbnail;
authors: authors:
| { | {
value: string; value: string;
@ -356,11 +479,11 @@ export interface Post {
categories?: categories?:
| { | {
value: string; value: string;
relationTo: 'tags'; relationTo: 'keys';
}[] }[]
| { | {
value: Tag; value: Key;
relationTo: 'tags'; relationTo: 'keys';
}[]; }[];
translations: { translations: {
language: string | Language; language: string | Language;
@ -375,10 +498,40 @@ export interface Post {
id?: string; id?: string;
}[]; }[];
publishedDate: string; publishedDate: string;
hidden?: boolean;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: 'draft' | 'published'; _status?: 'draft' | 'published';
} }
export interface PostThumbnail {
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;
};
medium?: {
url?: string;
width?: number;
height?: number;
mimeType?: string;
filesize?: number;
filename?: string;
};
};
}
export interface User { export interface User {
id: string; id: string;
email: string; email: string;

View File

@ -0,0 +1,95 @@
import { Block, BlockField } from "payload/types";
const isDefined = <T>(value: T | null | undefined): value is T =>
value !== null && value !== undefined;
const recursionFieldName = "recursion" as const;
type BlockConfig<T extends string> = {
root: boolean;
block: RecursiveBlock<T> | Block;
};
type RecursiveBlock<T extends string> = Omit<Block, "fields" | "interfaceName"> & {
[recursionFieldName]: Omit<BlockField, "blocks" | "type"> & {
newDepth: (currentDepth: number) => number;
condition: (currentDepth: number, parents: T[]) => boolean;
blocks: T[];
};
fields?: Block["fields"];
};
export type BlocksConfig<T extends string> = Record<T, BlockConfig<T>>;
export const generateBlocks = <T extends string>(blocksConfig: BlocksConfig<T>): Block[] => {
const isRecursiveBlock = (block: RecursiveBlock<T> | Block): block is RecursiveBlock<T> =>
recursionFieldName in block;
const getInterfaceName = (parents: T[], currentBlockName: T): string => {
const capitalize = (text: string): string => {
if (text.length === 0) return text;
const [firstLetter, ...rest] = text;
return [firstLetter.toUpperCase(), ...rest].join("");
};
return [...parents, currentBlockName]
.map((blockName) => blocksConfig[blockName].block.slug)
.map(capitalize)
.join("_");
};
const getCurrentDepth = (parents: T[]): number =>
parents.reduce((acc, blockName) => {
const block = blocksConfig[blockName].block;
if (!isRecursiveBlock(block)) return acc;
return block[recursionFieldName].newDepth(acc);
}, 1);
const generateRecursiveBlocks = (parents: T[], blockName: T): Block | undefined => {
const currentDepth = getCurrentDepth(parents);
const block = blocksConfig[blockName].block;
if (!isRecursiveBlock(block)) return block;
const {
slug,
labels,
fields = [],
recursion: { newDepth, blocks, condition, ...fieldsProps },
} = block;
const generatedBlocks = blocks
.filter((blockName) => {
const block = blocksConfig[blockName].block;
if (!isRecursiveBlock(block)) return true;
return block[recursionFieldName].condition(currentDepth, parents);
})
.map((nextBlockName) => generateRecursiveBlocks([...parents, blockName], nextBlockName))
.filter(isDefined);
// Cut dead branches (branches without leafs)
if (generatedBlocks.length === 0) {
return undefined;
}
return {
slug,
interfaceName: getInterfaceName(parents, blockName),
labels,
fields: [
...fields,
{
...fieldsProps,
type: "blocks",
blocks: generatedBlocks,
},
],
};
};
const rootBlockNames = Object.entries<BlockConfig<T>>(blocksConfig)
.filter(([_, blockConfig]) => blockConfig.root)
.map(([blockName]) => blockName as T);
return rootBlockNames
.map((blockName) => generateRecursiveBlocks([], blockName))
.filter(isDefined);
};

View File

@ -1,11 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "ES2022",
"lib": [ "moduleResolution": "NodeNext",
"dom", "lib": ["dom", "dom.iterable", "esnext"],
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"strict": false, "strict": false,
"esModuleInterop": true, "esModuleInterop": true,
@ -14,21 +11,13 @@
"rootDir": "./src", "rootDir": "./src",
"jsx": "react", "jsx": "react",
"paths": { "paths": {
"payload/generated-types": [ "payload/generated-types": ["./src/payload-types.ts"]
"./src/payload-types.ts",
],
} }
}, },
"include": [ "include": ["src"],
"src" "exclude": ["node_modules", "dist", "build"],
],
"exclude": [
"node_modules",
"dist",
"build",
],
"ts-node": { "ts-node": {
"transpileOnly": true, "transpileOnly": true,
"swc": true, "swc": true
} }
} }