Added more collections
This commit is contained in:
parent
138d7ae571
commit
84f0b20999
|
@ -13,7 +13,8 @@
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"iso-639-1": "^2.1.15",
|
"iso-639-1": "^2.1.15",
|
||||||
"payload": "^1.11.1"
|
"payload": "^1.11.1",
|
||||||
|
"slugify": "^1.6.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.9",
|
"@types/express": "^4.17.9",
|
||||||
|
@ -8504,6 +8505,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
|
||||||
"integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA=="
|
"integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/slugify": {
|
||||||
|
"version": "1.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
|
||||||
|
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/smart-buffer": {
|
"node_modules/smart-buffer": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||||
|
|
|
@ -22,7 +22,8 @@
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"iso-639-1": "^2.1.15",
|
"iso-639-1": "^2.1.15",
|
||||||
"payload": "^1.11.1"
|
"payload": "^1.11.1",
|
||||||
|
"slugify": "^1.6.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.9",
|
"@types/express": "^4.17.9",
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { CollectionConfig } from "payload/types";
|
|
||||||
import { localizedFields } from "../../elements/translatedFields/translatedFields";
|
|
||||||
|
|
||||||
const fields = {
|
|
||||||
id: "id",
|
|
||||||
translations: "translations",
|
|
||||||
name: "name",
|
|
||||||
short: "short",
|
|
||||||
} as const satisfies Record<string, string>;
|
|
||||||
|
|
||||||
const labels = {
|
|
||||||
singular: "Category",
|
|
||||||
plural: "Categories",
|
|
||||||
} as const satisfies { singular: string; plural: string };
|
|
||||||
|
|
||||||
export const Categories: CollectionConfig = {
|
|
||||||
slug: labels.plural,
|
|
||||||
labels,
|
|
||||||
typescript: { interface: labels.singular },
|
|
||||||
defaultSort: fields.id,
|
|
||||||
admin: {
|
|
||||||
useAsTitle: fields.id,
|
|
||||||
defaultColumns: [fields.id, fields.translations],
|
|
||||||
},
|
|
||||||
timestamps: false,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: fields.id,
|
|
||||||
type: "text",
|
|
||||||
},
|
|
||||||
localizedFields({
|
|
||||||
name: fields.translations,
|
|
||||||
interfaceName: "CategoryTranslations",
|
|
||||||
admin: {
|
|
||||||
useAsTitle: fields.name,
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
type: "row",
|
|
||||||
fields: [
|
|
||||||
{ name: fields.name, type: "text", required: true },
|
|
||||||
{ name: fields.short, type: "text" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
import { Block, BlockField } from "payload/types";
|
||||||
|
import { cueBlock } from "./cueBlock";
|
||||||
|
import { textBlock } from "./textBlock";
|
||||||
|
import { transcriptBlock } from "./transcriptBlock";
|
||||||
|
import { lineBlock } from "./lineBlock";
|
||||||
|
|
||||||
|
const INITIAL_DEPTH = 1;
|
||||||
|
const MAX_DEPTH = 4;
|
||||||
|
|
||||||
|
enum BlockName {
|
||||||
|
Text = "Text",
|
||||||
|
Section = "Section",
|
||||||
|
Tabs = "Tabs",
|
||||||
|
Tab = "Tab",
|
||||||
|
Columns = "Columns",
|
||||||
|
Column = "Column",
|
||||||
|
Transcript = "Transcript",
|
||||||
|
Collapsible = "Collapsible",
|
||||||
|
Accordion = "Accordion",
|
||||||
|
Line = "Line",
|
||||||
|
Cue = "Cue",
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootBlocksNames: BlockName[] = [
|
||||||
|
BlockName.Section,
|
||||||
|
BlockName.Collapsible,
|
||||||
|
BlockName.Columns,
|
||||||
|
BlockName.Tabs,
|
||||||
|
BlockName.Accordion,
|
||||||
|
BlockName.Text,
|
||||||
|
BlockName.Transcript,
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
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",
|
||||||
|
interfaceName: "Section",
|
||||||
|
labels: { singular: "Section", plural: "Sections" },
|
||||||
|
fields: {
|
||||||
|
name: "content",
|
||||||
|
newDepth: (depth) => depth + 1,
|
||||||
|
blocks: blocksChildren.Section,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Accordion: (currentDepth) =>
|
||||||
|
implementationForRecursiveBlocks(currentDepth, {
|
||||||
|
slug: "accordion",
|
||||||
|
interfaceName: "Accordion",
|
||||||
|
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",
|
||||||
|
interfaceName: "Tabs",
|
||||||
|
labels: { singular: "Tabs", plural: "Tabs" },
|
||||||
|
fields: { name: "tabs", newDepth: (depth) => depth, blocks: blocksChildren.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,
|
||||||
|
blocks: blocksChildren.Columns,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Column: (currentDepth) =>
|
||||||
|
implementationForRecursiveBlocks(currentDepth, {
|
||||||
|
slug: "column",
|
||||||
|
interfaceName: "Column",
|
||||||
|
labels: { singular: "Column", plural: "Columns" },
|
||||||
|
fields: { name: "content", newDepth: (depth) => depth + 1, blocks: blocksChildren.Column },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rootBlocks: Block[] = rootBlocksNames
|
||||||
|
.filter((block) => block in implementations)
|
||||||
|
.map((block) => implementations[block](INITIAL_DEPTH));
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { Block } from "payload/types";
|
||||||
|
|
||||||
|
export const cueBlock: Block = {
|
||||||
|
slug: "cueBlock",
|
||||||
|
interfaceName: "CueBlock",
|
||||||
|
labels: { singular: "Cue", plural: "Cues" },
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "content",
|
||||||
|
label: false,
|
||||||
|
type: "textarea",
|
||||||
|
required: true,
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"Parenthesis will automatically be added around cues. You don't have to include them here.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Block } from "payload/types";
|
||||||
|
|
||||||
|
export const lineBlock: Block = {
|
||||||
|
slug: "lineBlock",
|
||||||
|
interfaceName: "LineBlock",
|
||||||
|
labels: { singular: "Line", plural: "Lines" },
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "content",
|
||||||
|
label: false,
|
||||||
|
type: "richText",
|
||||||
|
required: true,
|
||||||
|
admin: {
|
||||||
|
hideGutter: true,
|
||||||
|
elements: [],
|
||||||
|
leaves: ["bold", "italic", "underline", "strikethrough", "code"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { Block } from "payload/types";
|
||||||
|
|
||||||
|
export const textBlock: Block = {
|
||||||
|
slug: "textBlock",
|
||||||
|
interfaceName: "TextBlock",
|
||||||
|
labels: { singular: "Text", plural: "Texts" },
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "content",
|
||||||
|
type: "richText",
|
||||||
|
label: false,
|
||||||
|
required: true,
|
||||||
|
admin: {
|
||||||
|
hideGutter: true,
|
||||||
|
elements: ["ul", "ol", "indent", "link", "relationship", "upload", "blockquote"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { Block } from "payload/types";
|
||||||
|
import { lineBlock } from "./lineBlock";
|
||||||
|
import { cueBlock } from "./cueBlock";
|
||||||
|
|
||||||
|
export const transcriptBlock: Block = {
|
||||||
|
slug: "transcriptBlock",
|
||||||
|
interfaceName: "TranscriptBlock",
|
||||||
|
labels: { singular: "Transcript", plural: "Transcripts" },
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "lines",
|
||||||
|
type: "blocks",
|
||||||
|
required: true,
|
||||||
|
minRows: 1,
|
||||||
|
admin: { initCollapsed: true },
|
||||||
|
blocks: [lineBlock, cueBlock],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,208 @@
|
||||||
|
import { CollectionConfig } from "payload/types";
|
||||||
|
import { collectionSlug } from "../../utils/string";
|
||||||
|
import { CollectionGroup, FileTypes, TagsTypes } from "../../constants";
|
||||||
|
import { slugField } from "../../fields/slugField/slugField";
|
||||||
|
import { imageField } from "../../fields/imageField/imageField";
|
||||||
|
import { Tags } from "../Tags/Tags";
|
||||||
|
import { localizedFields } from "../../fields/translatedFields/translatedFields";
|
||||||
|
import { Recorders } from "../Recorders/Recorders";
|
||||||
|
import { isDefined } from "../../utils/asserts";
|
||||||
|
import { fileField } from "../../fields/fileField/fileField";
|
||||||
|
import { rootBlocks } from "./Blocks/blocks";
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
slug: "slug",
|
||||||
|
thumbnail: "thumbnail",
|
||||||
|
categories: "categories",
|
||||||
|
type: "type",
|
||||||
|
translations: "translations",
|
||||||
|
pretitle: "pretitle",
|
||||||
|
title: "title",
|
||||||
|
subtitle: "subtitle",
|
||||||
|
summary: "summary",
|
||||||
|
textContent: "textContent",
|
||||||
|
textTranscribers: "textTranscribers",
|
||||||
|
textTranslators: "textTranslators",
|
||||||
|
textProofreaders: "textProofreaders",
|
||||||
|
textNotes: "textNotes",
|
||||||
|
video: "video",
|
||||||
|
videoNotes: "videoNotes",
|
||||||
|
audio: "audio",
|
||||||
|
audioNotes: "videoNotes",
|
||||||
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
singular: "Content",
|
||||||
|
plural: "Contents",
|
||||||
|
} as const satisfies { singular: string; plural: string };
|
||||||
|
|
||||||
|
export const Contents: CollectionConfig = {
|
||||||
|
slug: collectionSlug(labels.plural),
|
||||||
|
labels,
|
||||||
|
typescript: { interface: labels.singular },
|
||||||
|
defaultSort: fields.slug,
|
||||||
|
admin: {
|
||||||
|
useAsTitle: fields.slug,
|
||||||
|
defaultColumns: [fields.slug, fields.thumbnail, fields.categories],
|
||||||
|
group: CollectionGroup.Collections,
|
||||||
|
preview: (doc) => `https://accords-library.com/contents/${doc.slug}`,
|
||||||
|
},
|
||||||
|
timestamps: true,
|
||||||
|
versions: { drafts: true },
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
slugField({ name: fields.slug, admin: { width: "50%" } }),
|
||||||
|
imageField({ name: fields.thumbnail, admin: { width: "50%" } }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "testing",
|
||||||
|
type: "blocks",
|
||||||
|
admin: { initCollapsed: true },
|
||||||
|
blocks: rootBlocks,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: fields.categories,
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: [Tags.slug],
|
||||||
|
filterOptions: { type: { equals: TagsTypes.Categories } },
|
||||||
|
hasMany: true,
|
||||||
|
admin: { allowCreate: false, width: "50%" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.type,
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: [Tags.slug],
|
||||||
|
filterOptions: { type: { equals: TagsTypes.Contents } },
|
||||||
|
admin: { allowCreate: false, width: "50%" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
localizedFields({
|
||||||
|
name: fields.translations,
|
||||||
|
admin: { useAsTitle: fields.title, hasSourceLanguage: true },
|
||||||
|
required: true,
|
||||||
|
minRows: 1,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
{ name: fields.pretitle, type: "text" },
|
||||||
|
{ name: fields.title, type: "text", required: true },
|
||||||
|
{ name: fields.subtitle, type: "text" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: fields.summary, type: "textarea" },
|
||||||
|
{
|
||||||
|
type: "tabs",
|
||||||
|
admin: {
|
||||||
|
condition: (_, siblingData) =>
|
||||||
|
isDefined(siblingData.language) && isDefined(siblingData.sourceLanguage),
|
||||||
|
},
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
label: "Text",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: fields.textTranscribers,
|
||||||
|
label: "Transcribers",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: Recorders.slug,
|
||||||
|
hasMany: true,
|
||||||
|
admin: {
|
||||||
|
condition: (_, siblingData) =>
|
||||||
|
siblingData.language === siblingData.sourceLanguage,
|
||||||
|
width: "50%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.textTranslators,
|
||||||
|
label: "Translators",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: Recorders.slug,
|
||||||
|
hasMany: true,
|
||||||
|
admin: {
|
||||||
|
condition: (_, siblingData) =>
|
||||||
|
siblingData.language !== siblingData.sourceLanguage,
|
||||||
|
width: "50%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.textProofreaders,
|
||||||
|
label: "Proofreaders",
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: Recorders.slug,
|
||||||
|
hasMany: true,
|
||||||
|
admin: { width: "50%" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.textContent,
|
||||||
|
label: "Content",
|
||||||
|
type: "richText",
|
||||||
|
admin: { hideGutter: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.textNotes,
|
||||||
|
label: "Notes",
|
||||||
|
type: "textarea",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Video",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
fileField({
|
||||||
|
name: fields.video,
|
||||||
|
filterOptions: { type: { equals: FileTypes.ContentVideo } },
|
||||||
|
admin: { width: "50%" },
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: fields.videoNotes,
|
||||||
|
label: "Notes",
|
||||||
|
type: "textarea",
|
||||||
|
admin: { width: "50%" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Audio",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
fileField({
|
||||||
|
name: fields.audio,
|
||||||
|
filterOptions: { type: { equals: FileTypes.ContentAudio } },
|
||||||
|
admin: { width: "50%" },
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: fields.audioNotes,
|
||||||
|
label: "Notes",
|
||||||
|
type: "textarea",
|
||||||
|
admin: { width: "50%" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { CollectionConfig } from "payload/types";
|
||||||
|
import { CollectionGroup, FileTypes } from "../../constants";
|
||||||
|
import { collectionSlug } from "../../utils/string";
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
filename: "filename",
|
||||||
|
type: "type",
|
||||||
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
singular: "File",
|
||||||
|
plural: "Files",
|
||||||
|
} as const satisfies { singular: string; plural: string };
|
||||||
|
|
||||||
|
export const Files: CollectionConfig = {
|
||||||
|
slug: collectionSlug(labels.plural),
|
||||||
|
labels,
|
||||||
|
typescript: { interface: labels.singular },
|
||||||
|
defaultSort: fields.filename,
|
||||||
|
admin: {
|
||||||
|
useAsTitle: fields.filename,
|
||||||
|
group: CollectionGroup.Media,
|
||||||
|
},
|
||||||
|
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: fields.filename,
|
||||||
|
required: true,
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.type,
|
||||||
|
type: "select",
|
||||||
|
required: true,
|
||||||
|
options: Object.entries(FileTypes).map(([value, label]) => ({ label, value })),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -1,4 +1,6 @@
|
||||||
import { CollectionConfig } from "payload/types";
|
import { CollectionConfig } from "payload/types";
|
||||||
|
import { CollectionGroup } from "../../constants";
|
||||||
|
import { collectionSlug } from "../../utils/string";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
filename: "filename",
|
filename: "filename",
|
||||||
|
@ -13,13 +15,13 @@ const labels = {
|
||||||
} as const satisfies { singular: string; plural: string };
|
} as const satisfies { singular: string; plural: string };
|
||||||
|
|
||||||
export const Images: CollectionConfig = {
|
export const Images: CollectionConfig = {
|
||||||
slug: labels.plural,
|
slug: collectionSlug(labels.plural),
|
||||||
labels,
|
labels,
|
||||||
typescript: { interface: labels.singular },
|
typescript: { interface: labels.singular },
|
||||||
defaultSort: fields.filename,
|
defaultSort: fields.filename,
|
||||||
admin: {
|
admin: {
|
||||||
useAsTitle: fields.filename,
|
useAsTitle: fields.filename,
|
||||||
group: "Media",
|
group: CollectionGroup.Media,
|
||||||
},
|
},
|
||||||
|
|
||||||
upload: {
|
upload: {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { CollectionConfig } from "payload/types";
|
import { CollectionConfig } from "payload/types";
|
||||||
|
import { CollectionGroup } from "../constants";
|
||||||
|
import { collectionSlug } from "../utils/string";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
id: "id",
|
id: "id",
|
||||||
|
@ -11,13 +13,14 @@ const labels = {
|
||||||
} as const satisfies { singular: string; plural: string };
|
} as const satisfies { singular: string; plural: string };
|
||||||
|
|
||||||
export const Languages: CollectionConfig = {
|
export const Languages: CollectionConfig = {
|
||||||
slug: labels.plural,
|
slug: collectionSlug(labels.plural),
|
||||||
labels,
|
labels,
|
||||||
typescript: { interface: labels.singular },
|
typescript: { interface: labels.singular },
|
||||||
defaultSort: fields.name,
|
defaultSort: fields.name,
|
||||||
admin: {
|
admin: {
|
||||||
useAsTitle: fields.name,
|
useAsTitle: fields.name,
|
||||||
defaultColumns: [fields.name, fields.id],
|
defaultColumns: [fields.name, fields.id],
|
||||||
|
group: CollectionGroup.Meta,
|
||||||
},
|
},
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
fields: [
|
fields: [
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
import { CollectionConfig } from "payload/types";
|
||||||
|
import { CollectionGroup } from "../../constants";
|
||||||
|
import { slugField } from "../../fields/slugField/slugField";
|
||||||
|
import { imageField } from "../../fields/imageField/imageField";
|
||||||
|
import { collectionSlug } from "../../utils/string";
|
||||||
|
import { isDefined, isUndefined } from "../../utils/asserts";
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
status: "status",
|
||||||
|
slug: "slug",
|
||||||
|
thumbnail: "thumbnail",
|
||||||
|
pretitle: "pretitle",
|
||||||
|
title: "title",
|
||||||
|
subtitle: "subtitle",
|
||||||
|
rootItem: "rootItem",
|
||||||
|
primary: "primary",
|
||||||
|
digital: "digital",
|
||||||
|
downloadable: "downloadable",
|
||||||
|
size: "size",
|
||||||
|
width: "width",
|
||||||
|
height: "height",
|
||||||
|
thickness: "thickness",
|
||||||
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
singular: "Library Item",
|
||||||
|
plural: "Library Items",
|
||||||
|
} as const satisfies { singular: string; plural: string };
|
||||||
|
|
||||||
|
const validateSizeValue = (value?: number) => {
|
||||||
|
if (isDefined(value) && value <= 0) return "This value must be greater than 0";
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateRequiredSizeValue = (value?: number) => {
|
||||||
|
if (isUndefined(value)) return "This field is required.";
|
||||||
|
if (value <= 0) return "This value must be greater than 0.";
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LibraryItems: CollectionConfig = {
|
||||||
|
slug: collectionSlug(labels.plural),
|
||||||
|
labels,
|
||||||
|
typescript: { interface: labels.singular },
|
||||||
|
defaultSort: fields.slug,
|
||||||
|
admin: {
|
||||||
|
useAsTitle: fields.slug,
|
||||||
|
defaultColumns: [fields.slug, fields.thumbnail, fields.status],
|
||||||
|
group: CollectionGroup.Collections,
|
||||||
|
preview: (doc) => `https://accords-library.com/library/${doc.slug}`,
|
||||||
|
},
|
||||||
|
timestamps: true,
|
||||||
|
versions: { drafts: true },
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
slugField({ name: fields.slug, admin: { width: "50%" } }),
|
||||||
|
imageField({ name: fields.thumbnail, admin: { width: "50%" } }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
{ name: fields.pretitle, type: "text" },
|
||||||
|
{ name: fields.title, type: "text", required: true },
|
||||||
|
{ name: fields.subtitle, type: "text" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: fields.rootItem,
|
||||||
|
type: "checkbox",
|
||||||
|
required: true,
|
||||||
|
defaultValue: true,
|
||||||
|
admin: {
|
||||||
|
description: "Only items that can be sold separetely should be root items.",
|
||||||
|
width: "25%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.primary,
|
||||||
|
type: "checkbox",
|
||||||
|
required: true,
|
||||||
|
defaultValue: true,
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"A primary item is an official item that focuses primarly on one or more of our Categories.",
|
||||||
|
width: "25%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.digital,
|
||||||
|
type: "checkbox",
|
||||||
|
required: true,
|
||||||
|
defaultValue: false,
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"The item is the digital version of another item, or the item is sold only digitally.",
|
||||||
|
width: "25%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.downloadable,
|
||||||
|
type: "checkbox",
|
||||||
|
required: true,
|
||||||
|
defaultValue: false,
|
||||||
|
admin: {
|
||||||
|
description: "Are the scans available for download?",
|
||||||
|
width: "25%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "size",
|
||||||
|
type: "group",
|
||||||
|
admin: { condition: (data) => !data.digital },
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: fields.width,
|
||||||
|
type: "number",
|
||||||
|
validate: validateRequiredSizeValue,
|
||||||
|
admin: { step: 1, width: "33%", description: "in mm." },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.height,
|
||||||
|
type: "number",
|
||||||
|
validate: validateRequiredSizeValue,
|
||||||
|
admin: { step: 1, width: "33%", description: "in mm." },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.thickness,
|
||||||
|
type: "number",
|
||||||
|
validate: validateSizeValue,
|
||||||
|
admin: { step: 1, width: "33%", description: "in mm." },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,142 @@
|
||||||
|
import { CollectionConfig } from "payload/types";
|
||||||
|
import { slugField } from "../../fields/slugField/slugField";
|
||||||
|
import { imageField } from "../../fields/imageField/imageField";
|
||||||
|
import { CollectionGroup, TagsTypes } from "../../constants";
|
||||||
|
import { Recorders } from "../Recorders/Recorders";
|
||||||
|
import { localizedFields } from "../../fields/translatedFields/translatedFields";
|
||||||
|
import { isDefined, isUndefined } from "../../utils/asserts";
|
||||||
|
import { removeTranslatorsForTranscripts } from "./hooks/beforeValidate";
|
||||||
|
import { Tags } from "../Tags/Tags";
|
||||||
|
import { collectionSlug } from "../../utils/string";
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
slug: "slug",
|
||||||
|
thumbnail: "thumbnail",
|
||||||
|
categories: "categories",
|
||||||
|
authors: "authors",
|
||||||
|
publishedDate: "publishedDate",
|
||||||
|
translations: "translations",
|
||||||
|
sourceLanguage: "sourceLanguage",
|
||||||
|
title: "title",
|
||||||
|
summary: "summary",
|
||||||
|
content: "content",
|
||||||
|
translators: "translators",
|
||||||
|
proofreaders: "proofreaders",
|
||||||
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
singular: "Post",
|
||||||
|
plural: "Posts",
|
||||||
|
} as const satisfies { singular: string; plural: string };
|
||||||
|
|
||||||
|
export const Posts: CollectionConfig = {
|
||||||
|
slug: collectionSlug(labels.plural),
|
||||||
|
labels,
|
||||||
|
typescript: { interface: labels.singular },
|
||||||
|
defaultSort: fields.slug,
|
||||||
|
admin: {
|
||||||
|
useAsTitle: fields.slug,
|
||||||
|
defaultColumns: [fields.slug, fields.thumbnail, fields.categories],
|
||||||
|
group: CollectionGroup.Collections,
|
||||||
|
preview: (doc) => `https://accords-library.com/news/${doc.slug}`,
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
beforeValidate: [removeTranslatorsForTranscripts],
|
||||||
|
},
|
||||||
|
timestamps: true,
|
||||||
|
versions: { drafts: true },
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
slugField({ name: fields.slug, admin: { width: "50%" } }),
|
||||||
|
imageField({ name: fields.thumbnail, admin: { width: "50%" } }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: fields.authors,
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: [Recorders.slug],
|
||||||
|
required: true,
|
||||||
|
minRows: 1,
|
||||||
|
hasMany: true,
|
||||||
|
admin: { width: "50%" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.categories,
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: [Tags.slug],
|
||||||
|
filterOptions: { type: { equals: TagsTypes.Categories } },
|
||||||
|
hasMany: true,
|
||||||
|
admin: { allowCreate: false, width: "50%" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
localizedFields({
|
||||||
|
name: fields.translations,
|
||||||
|
admin: { useAsTitle: fields.title, hasSourceLanguage: true },
|
||||||
|
required: true,
|
||||||
|
minRows: 1,
|
||||||
|
fields: [
|
||||||
|
{ name: fields.title, type: "text", required: true },
|
||||||
|
{ name: fields.summary, type: "textarea" },
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: fields.translators,
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: Recorders.slug,
|
||||||
|
hasMany: true,
|
||||||
|
admin: {
|
||||||
|
condition: (_, siblingData) => {
|
||||||
|
if (
|
||||||
|
isUndefined(siblingData.language) ||
|
||||||
|
isUndefined(siblingData.sourceLanguage)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return siblingData.language !== siblingData.sourceLanguage;
|
||||||
|
},
|
||||||
|
width: "50%",
|
||||||
|
},
|
||||||
|
validate: (translators, { siblingData }) => {
|
||||||
|
if (isUndefined(siblingData.language) || isUndefined(siblingData.sourceLanguage)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (siblingData.language === siblingData.sourceLanguage) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isDefined(translators) && translators.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return "This field is required when the language is different from the source language.";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fields.proofreaders,
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: Recorders.slug,
|
||||||
|
hasMany: true,
|
||||||
|
admin: { width: "50%" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: fields.content, type: "richText", admin: { hideGutter: true } },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: fields.publishedDate,
|
||||||
|
type: "date",
|
||||||
|
defaultValue: new Date().toISOString(),
|
||||||
|
admin: {
|
||||||
|
date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" },
|
||||||
|
position: "sidebar",
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { CollectionBeforeValidateHook } from "payload/types";
|
||||||
|
import { Post } from "../../../types/collections";
|
||||||
|
|
||||||
|
export const removeTranslatorsForTranscripts: CollectionBeforeValidateHook<Post> = async ({
|
||||||
|
data: { translations, ...data },
|
||||||
|
}) => ({
|
||||||
|
...data,
|
||||||
|
translations: translations.map(({ translators, ...translation }) => {
|
||||||
|
if (translation.language === translation.sourceLanguage) {
|
||||||
|
return { ...translation, translators: [] };
|
||||||
|
}
|
||||||
|
return { ...translation, translators };
|
||||||
|
}),
|
||||||
|
});
|
|
@ -1,9 +1,12 @@
|
||||||
import { CollectionConfig } from "payload/types";
|
import { CollectionConfig } from "payload/types";
|
||||||
import { localizedFields } from "../../elements/translatedFields/translatedFields";
|
import { localizedFields } from "../../fields/translatedFields/translatedFields";
|
||||||
import { Languages } from "../Languages";
|
import { Languages } from "../Languages";
|
||||||
import { Images } from "../Images/Images";
|
import { Images } from "../Images/Images";
|
||||||
import { ImageCell } from "../Images/components/ImageCell";
|
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 { collectionSlug } from "../../utils/string";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
username: "username",
|
username: "username",
|
||||||
|
@ -20,7 +23,7 @@ const labels = {
|
||||||
} as const satisfies { singular: string; plural: string };
|
} as const satisfies { singular: string; plural: string };
|
||||||
|
|
||||||
export const Recorders: CollectionConfig = {
|
export const Recorders: CollectionConfig = {
|
||||||
slug: labels.plural,
|
slug: collectionSlug(labels.plural),
|
||||||
labels,
|
labels,
|
||||||
typescript: { interface: labels.singular },
|
typescript: { interface: labels.singular },
|
||||||
defaultSort: fields.username,
|
defaultSort: fields.username,
|
||||||
|
@ -30,22 +33,14 @@ export const Recorders: CollectionConfig = {
|
||||||
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.anonymize, fields.biographies, fields.languages],
|
||||||
|
group: CollectionGroup.Meta,
|
||||||
},
|
},
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
type: "row",
|
type: "row",
|
||||||
fields: [
|
fields: [
|
||||||
{
|
imageField({ name: fields.avatar }),
|
||||||
name: fields.avatar,
|
|
||||||
type: "upload",
|
|
||||||
relationTo: Images.slug,
|
|
||||||
admin: {
|
|
||||||
components: {
|
|
||||||
Cell: ImageCell,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: fields.username,
|
name: fields.username,
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { CollectionConfig } from "payload/types";
|
||||||
|
import { slugField } from "../../fields/slugField/slugField";
|
||||||
|
import { CollectionGroup, TagsTypes } from "../../constants";
|
||||||
|
import { localizedFields } from "../../fields/translatedFields/translatedFields";
|
||||||
|
import { collectionSlug } from "../../utils/string";
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
slug: "slug",
|
||||||
|
translations: "translations",
|
||||||
|
type: "type",
|
||||||
|
name: "name",
|
||||||
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
singular: "Tag",
|
||||||
|
plural: "Tags",
|
||||||
|
} as const satisfies { singular: string; plural: string };
|
||||||
|
|
||||||
|
export const Tags: CollectionConfig = {
|
||||||
|
slug: collectionSlug(labels.plural),
|
||||||
|
labels,
|
||||||
|
typescript: { interface: labels.singular },
|
||||||
|
defaultSort: fields.slug,
|
||||||
|
admin: {
|
||||||
|
useAsTitle: fields.slug,
|
||||||
|
defaultColumns: [fields.slug, fields.type, fields.translations],
|
||||||
|
group: CollectionGroup.Meta,
|
||||||
|
},
|
||||||
|
timestamps: false,
|
||||||
|
versions: false,
|
||||||
|
fields: [
|
||||||
|
slugField({ name: fields.slug }),
|
||||||
|
{
|
||||||
|
name: fields.type,
|
||||||
|
type: "select",
|
||||||
|
required: true,
|
||||||
|
options: Object.entries(TagsTypes).map(([value, label]) => ({ label, value })),
|
||||||
|
},
|
||||||
|
localizedFields({
|
||||||
|
name: fields.translations,
|
||||||
|
interfaceName: "CategoryTranslations",
|
||||||
|
admin: {
|
||||||
|
useAsTitle: fields.name,
|
||||||
|
},
|
||||||
|
fields: [{ name: fields.name, type: "text", required: true }],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
|
@ -1,4 +1,6 @@
|
||||||
import { CollectionConfig } from "payload/types";
|
import { CollectionConfig } from "payload/types";
|
||||||
|
import { CollectionGroup } from "../constants";
|
||||||
|
import { collectionSlug } from "../utils/string";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
email: "email",
|
email: "email",
|
||||||
|
@ -10,7 +12,7 @@ const labels = {
|
||||||
} as const satisfies { singular: string; plural: string };
|
} as const satisfies { singular: string; plural: string };
|
||||||
|
|
||||||
export const Users: CollectionConfig = {
|
export const Users: CollectionConfig = {
|
||||||
slug: labels.plural,
|
slug: collectionSlug(labels.plural),
|
||||||
auth: true,
|
auth: true,
|
||||||
labels,
|
labels,
|
||||||
typescript: { interface: labels.singular },
|
typescript: { interface: labels.singular },
|
||||||
|
@ -18,7 +20,7 @@ export const Users: CollectionConfig = {
|
||||||
admin: {
|
admin: {
|
||||||
useAsTitle: fields.email,
|
useAsTitle: fields.email,
|
||||||
defaultColumns: [fields.email],
|
defaultColumns: [fields.email],
|
||||||
group: "Administration",
|
group: CollectionGroup.Administration,
|
||||||
},
|
},
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
fields: [],
|
fields: [],
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
export enum CollectionGroup {
|
||||||
|
Collections = "Collections",
|
||||||
|
Media = "Media",
|
||||||
|
Meta = "Meta",
|
||||||
|
Administration = "Administration",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TagsTypes {
|
||||||
|
Contents = "Contents",
|
||||||
|
LibraryAudio = "Library / Audio",
|
||||||
|
LibraryVideo = "Library / Video",
|
||||||
|
LibraryTextual = "Library / Textual",
|
||||||
|
LibraryGroup = "Library / Group",
|
||||||
|
Library = "Library",
|
||||||
|
Weapons = "Weapons",
|
||||||
|
GamePlatforms = "Game Platforms",
|
||||||
|
Categories = "Categories",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FileTypes {
|
||||||
|
LibraryScans = "Library / Scans",
|
||||||
|
LibrarySoundtracks = "Library / Soundtracks",
|
||||||
|
ContentVideo = "Content / Video",
|
||||||
|
ContentAudio = "Content / Audio",
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { RelationshipField, UploadField } from "payload/types";
|
||||||
|
import { Files } from "../../collections/Files/Files";
|
||||||
|
|
||||||
|
type Props = Omit<UploadField, "type" | "relationTo">;
|
||||||
|
|
||||||
|
export const fileField = (props: Props): RelationshipField => ({
|
||||||
|
...props,
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: Files.slug,
|
||||||
|
});
|
|
@ -2,7 +2,7 @@ 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";
|
||||||
|
|
||||||
export const ImageCell = ({ 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 () => {
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { UploadField } from "payload/types";
|
||||||
|
import { Images } from "../../collections/Images/Images";
|
||||||
|
import { Cell } from "./Cell";
|
||||||
|
|
||||||
|
type Props = Omit<UploadField, "type" | "relationTo">;
|
||||||
|
|
||||||
|
export const imageField = ({ admin, ...otherProps }: Props): UploadField => ({
|
||||||
|
...otherProps,
|
||||||
|
type: "upload",
|
||||||
|
relationTo: Images.slug,
|
||||||
|
admin: {
|
||||||
|
components: {
|
||||||
|
Cell,
|
||||||
|
},
|
||||||
|
...admin,
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { TextField } from "payload/types";
|
||||||
|
import { isUndefined } from "../../utils/asserts";
|
||||||
|
|
||||||
|
type Props = Omit<TextField, "type">;
|
||||||
|
|
||||||
|
const validateSlug = (value?: string) => {
|
||||||
|
if (isUndefined(value) || value === "") return "This field is required.";
|
||||||
|
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) return "This is not a valid slug.";
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const slugField = ({ admin, ...otherProps }: Props): TextField => ({
|
||||||
|
...otherProps,
|
||||||
|
type: "text",
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
validate: validateSlug,
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
"A slug must only include lowercase letters and digits. Instead of spaces you can use dashes.",
|
||||||
|
...admin,
|
||||||
|
},
|
||||||
|
});
|
|
@ -11,7 +11,7 @@ export const Cell = ({ cellData }: Props): JSX.Element => (
|
||||||
<div style={{ display: "flex", gap: "6px" }}>
|
<div style={{ display: "flex", gap: "6px" }}>
|
||||||
{cellData.map(({ language, title }, index) => (
|
{cellData.map(({ language, title }, index) => (
|
||||||
<div key={index} className="pill">
|
<div key={index} className="pill">
|
||||||
<abbr title={title} style={{textDecorationColor: "var(--color-base-500)"}}>
|
<abbr title={title} style={{ textDecorationColor: "var(--color-base-500)" }}>
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
{isDefined(language) && typeof language === "string"
|
{isDefined(language) && typeof language === "string"
|
||||||
? formatLanguageCode(language)
|
? formatLanguageCode(language)
|
|
@ -1,21 +1,40 @@
|
||||||
import { ArrayField } from "payload/types";
|
import { ArrayField, Field } from "payload/types";
|
||||||
import { hasDuplicates } from "../../utils/validation";
|
import { hasDuplicates } from "../../utils/validation";
|
||||||
import { isDefined, isUndefined } from "../../utils/asserts";
|
import { isDefined, isUndefined } from "../../utils/asserts";
|
||||||
import { Languages } from "../../collections/Languages";
|
import { Languages } from "../../collections/Languages";
|
||||||
import { RowLabel } from "./RowLabel";
|
import { RowLabel } from "./RowLabel";
|
||||||
import { Cell } from "./Cell";
|
import { Cell } from "./Cell";
|
||||||
|
|
||||||
const LANGUAGE_FIELD_NAME = "language";
|
const fieldsNames = {
|
||||||
|
language: "language",
|
||||||
|
sourceLanguage: "sourceLanguage",
|
||||||
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
type LocalizedFieldsProps = Omit<ArrayField, "type" | "admin"> & {
|
type LocalizedFieldsProps = Omit<ArrayField, "type" | "admin"> & {
|
||||||
admin?: ArrayField["admin"] & { useAsTitle?: string };
|
admin?: ArrayField["admin"] & { useAsTitle?: string; hasSourceLanguage?: boolean };
|
||||||
|
};
|
||||||
|
type ArrayData = { [fieldsNames.language]?: string }[] | number | undefined;
|
||||||
|
|
||||||
|
const languageField: Field = {
|
||||||
|
name: fieldsNames.language,
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: Languages.slug,
|
||||||
|
required: true,
|
||||||
|
admin: { allowCreate: false, width: "50%" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const sourceLanguageField: Field = {
|
||||||
|
name: fieldsNames.sourceLanguage,
|
||||||
|
type: "relationship",
|
||||||
|
relationTo: Languages.slug,
|
||||||
|
required: true,
|
||||||
|
admin: { allowCreate: false, width: "50%" },
|
||||||
};
|
};
|
||||||
type ArrayData = { [LANGUAGE_FIELD_NAME]?: string }[] | number | undefined;
|
|
||||||
|
|
||||||
export const localizedFields = ({
|
export const localizedFields = ({
|
||||||
fields,
|
fields,
|
||||||
validate,
|
validate,
|
||||||
admin: { useAsTitle, ...admin },
|
admin: { useAsTitle, hasSourceLanguage, ...admin },
|
||||||
...otherProps
|
...otherProps
|
||||||
}: LocalizedFieldsProps): ArrayField => ({
|
}: LocalizedFieldsProps): ArrayField => ({
|
||||||
...otherProps,
|
...otherProps,
|
||||||
|
@ -45,19 +64,15 @@ export const localizedFields = ({
|
||||||
|
|
||||||
const languages = data.map((biography) => biography.language);
|
const languages = data.map((biography) => biography.language);
|
||||||
if (hasDuplicates(languages)) {
|
if (hasDuplicates(languages)) {
|
||||||
return `There cannot be multiple ${otherProps.name} with the same ${LANGUAGE_FIELD_NAME}`;
|
return `There cannot be multiple ${otherProps.name} with the same ${fieldsNames.language}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isDefined(validate) ? validate(value, options) : true;
|
return isDefined(validate) ? validate(value, options) : true;
|
||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
{
|
hasSourceLanguage
|
||||||
name: LANGUAGE_FIELD_NAME,
|
? { type: "row", fields: [languageField, sourceLanguageField] }
|
||||||
type: "relationship",
|
: languageField,
|
||||||
relationTo: Languages.slug,
|
|
||||||
required: true,
|
|
||||||
admin: { allowCreate: false },
|
|
||||||
},
|
|
||||||
...fields,
|
...fields,
|
||||||
],
|
],
|
||||||
});
|
});
|
|
@ -4,14 +4,18 @@ 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 { Images } from "./collections/Images/Images";
|
||||||
import { Categories } from "./collections/Categories/Categories";
|
import { Posts } from "./collections/Posts/Posts";
|
||||||
|
import { Tags } from "./collections/Tags/Tags";
|
||||||
|
import { LibraryItems } from "./collections/LibraryItems/LibraryItems";
|
||||||
|
import { Contents } from "./collections/Contents/Contents";
|
||||||
|
import { Files } from "./collections/Files/Files";
|
||||||
|
|
||||||
export default buildConfig({
|
export default buildConfig({
|
||||||
serverURL: "http://localhost:3000",
|
serverURL: "http://localhost:3000",
|
||||||
admin: {
|
admin: {
|
||||||
user: Users.slug,
|
user: Users.slug,
|
||||||
},
|
},
|
||||||
collections: [Users, Languages, Recorders, Images, Categories],
|
collections: [LibraryItems, Contents, Posts, Images, Files, Languages, Recorders, Tags, Users],
|
||||||
globals: [],
|
globals: [],
|
||||||
telemetry: false,
|
telemetry: false,
|
||||||
typescript: {
|
typescript: {
|
||||||
|
|
|
@ -6,6 +6,11 @@
|
||||||
* and re-run `payload generate:types` to regenerate this file.
|
* and re-run `payload generate:types` to regenerate this file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type CategoryTranslations = {
|
||||||
|
language: string | Language;
|
||||||
|
name: string;
|
||||||
|
id?: string;
|
||||||
|
}[];
|
||||||
export type RecorderBiographies = {
|
export type RecorderBiographies = {
|
||||||
language: string | Language;
|
language: string | Language;
|
||||||
biography?: string;
|
biography?: string;
|
||||||
|
@ -14,35 +19,37 @@ export type RecorderBiographies = {
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
collections: {
|
collections: {
|
||||||
Users: User;
|
'library-items': LibraryItem;
|
||||||
Languages: Language;
|
contents: Content;
|
||||||
Recorders: Recorder;
|
posts: Post;
|
||||||
Images: Image;
|
images: Image;
|
||||||
|
files: File;
|
||||||
|
languages: Language;
|
||||||
|
recorders: Recorder;
|
||||||
|
tags: Tag;
|
||||||
|
users: User;
|
||||||
};
|
};
|
||||||
globals: {};
|
globals: {};
|
||||||
}
|
}
|
||||||
export interface User {
|
export interface LibraryItem {
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
slug: string;
|
||||||
resetPasswordToken?: string;
|
thumbnail?: string | Image;
|
||||||
resetPasswordExpiration?: string;
|
pretitle?: string;
|
||||||
salt?: string;
|
title: string;
|
||||||
hash?: string;
|
subtitle?: string;
|
||||||
loginAttempts?: number;
|
rootItem: boolean;
|
||||||
lockUntil?: string;
|
primary: boolean;
|
||||||
password?: string;
|
digital: boolean;
|
||||||
}
|
downloadable: boolean;
|
||||||
export interface Language {
|
size?: {
|
||||||
id: string;
|
width?: number;
|
||||||
name: string;
|
height?: number;
|
||||||
}
|
thickness?: number;
|
||||||
export interface Recorder {
|
};
|
||||||
id: string;
|
updatedAt: string;
|
||||||
avatar?: string | Image;
|
createdAt: string;
|
||||||
username: string;
|
_status?: 'draft' | 'published';
|
||||||
anonymize: boolean;
|
|
||||||
languages?: string[] | Language[];
|
|
||||||
biographies?: RecorderBiographies;
|
|
||||||
}
|
}
|
||||||
export interface Image {
|
export interface Image {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -56,3 +63,330 @@ export interface Image {
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
}
|
}
|
||||||
|
export interface Content {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
thumbnail?: string | Image;
|
||||||
|
testing?: (Section_1 | Collapsible_1 | Columns_1 | Tabs_1 | Accordion_1 | TextBlock | TranscriptBlock)[];
|
||||||
|
categories?:
|
||||||
|
| {
|
||||||
|
value: string;
|
||||||
|
relationTo: 'tags';
|
||||||
|
}[]
|
||||||
|
| {
|
||||||
|
value: Tag;
|
||||||
|
relationTo: 'tags';
|
||||||
|
}[];
|
||||||
|
type?: {
|
||||||
|
value: string | Tag;
|
||||||
|
relationTo: 'tags';
|
||||||
|
};
|
||||||
|
translations: {
|
||||||
|
language: string | Language;
|
||||||
|
sourceLanguage: string | Language;
|
||||||
|
pretitle?: string;
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
summary?: string;
|
||||||
|
textTranscribers?: string[] | Recorder[];
|
||||||
|
textTranslators?: string[] | Recorder[];
|
||||||
|
textProofreaders?: string[] | Recorder[];
|
||||||
|
textContent?: {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
textNotes?: string;
|
||||||
|
video?: string | File;
|
||||||
|
videoNotes?: string;
|
||||||
|
audio?: string | File;
|
||||||
|
id?: string;
|
||||||
|
}[];
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
_status?: 'draft' | 'published';
|
||||||
|
}
|
||||||
|
export interface Section_1 {
|
||||||
|
content?: (Section_2 | Collapsible_2 | Columns_2 | Tabs_2 | Accordion_2 | TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'section_1';
|
||||||
|
}
|
||||||
|
export interface Section_2 {
|
||||||
|
content?: (Section_3 | Collapsible_3 | Columns_3 | Tabs_3 | Accordion_3 | TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'section_2';
|
||||||
|
}
|
||||||
|
export interface Section_3 {
|
||||||
|
content?: (Section_4 | Collapsible_4 | Columns_4 | Tabs_4 | Accordion_4 | TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'section_3';
|
||||||
|
}
|
||||||
|
export interface Section_4 {
|
||||||
|
content?: (TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'section_4';
|
||||||
|
}
|
||||||
|
export interface TextBlock {
|
||||||
|
content: {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'textBlock';
|
||||||
|
}
|
||||||
|
export interface TranscriptBlock {
|
||||||
|
lines: (LineBlock | CueBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'transcriptBlock';
|
||||||
|
}
|
||||||
|
export interface LineBlock {
|
||||||
|
content: {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'lineBlock';
|
||||||
|
}
|
||||||
|
export interface CueBlock {
|
||||||
|
content: string;
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'cueBlock';
|
||||||
|
}
|
||||||
|
export interface Collapsible_4 {
|
||||||
|
content?: (TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'collapsible_4';
|
||||||
|
}
|
||||||
|
export interface Columns_4 {
|
||||||
|
columns?: Column_4[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'columns_4';
|
||||||
|
}
|
||||||
|
export interface Column_4 {
|
||||||
|
content?: (TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'column_4';
|
||||||
|
}
|
||||||
|
export interface Tabs_4 {
|
||||||
|
tabs?: Tab_4[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'tabs_4';
|
||||||
|
}
|
||||||
|
export interface Tab_4 {
|
||||||
|
content?: (TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'tab_4';
|
||||||
|
}
|
||||||
|
export interface Accordion_4 {
|
||||||
|
content?: Collapsible_5[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'accordion_4';
|
||||||
|
}
|
||||||
|
export interface Collapsible_5 {
|
||||||
|
content?: (TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'collapsible_5';
|
||||||
|
}
|
||||||
|
export interface Collapsible_3 {
|
||||||
|
content?: (Section_4 | Collapsible_4 | Columns_4 | Tabs_4 | Accordion_4 | TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'collapsible_3';
|
||||||
|
}
|
||||||
|
export interface Columns_3 {
|
||||||
|
columns?: Column_3[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'columns_3';
|
||||||
|
}
|
||||||
|
export interface Column_3 {
|
||||||
|
content?: (Section_4 | Collapsible_4 | Columns_4 | Tabs_4 | Accordion_4 | TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'column_3';
|
||||||
|
}
|
||||||
|
export interface Tabs_3 {
|
||||||
|
tabs?: Tab_3[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'tabs_3';
|
||||||
|
}
|
||||||
|
export interface Tab_3 {
|
||||||
|
content?: (Section_4 | Collapsible_4 | Columns_4 | Tabs_4 | Accordion_4 | TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'tab_3';
|
||||||
|
}
|
||||||
|
export interface Accordion_3 {
|
||||||
|
content?: Collapsible_4[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'accordion_3';
|
||||||
|
}
|
||||||
|
export interface Collapsible_2 {
|
||||||
|
content?: (Section_3 | Collapsible_3 | Columns_3 | Tabs_3 | Accordion_3 | TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'collapsible_2';
|
||||||
|
}
|
||||||
|
export interface Columns_2 {
|
||||||
|
columns?: Column_2[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'columns_2';
|
||||||
|
}
|
||||||
|
export interface Column_2 {
|
||||||
|
content?: (Section_3 | Collapsible_3 | Columns_3 | Tabs_3 | Accordion_3 | TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'column_2';
|
||||||
|
}
|
||||||
|
export interface Tabs_2 {
|
||||||
|
tabs?: Tab_2[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'tabs_2';
|
||||||
|
}
|
||||||
|
export interface Tab_2 {
|
||||||
|
content?: (Section_3 | Collapsible_3 | Columns_3 | Tabs_3 | Accordion_3 | TextBlock | TranscriptBlock)[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'tab_2';
|
||||||
|
}
|
||||||
|
export interface Accordion_2 {
|
||||||
|
content?: Collapsible_3[];
|
||||||
|
id?: string;
|
||||||
|
blockName?: string;
|
||||||
|
blockType: 'accordion_2';
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
id: string;
|
||||||
|
filename: string;
|
||||||
|
type: 'LibraryScans' | 'LibrarySoundtracks' | 'ContentVideo' | 'ContentAudio';
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
export interface Post {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
thumbnail?: string | Image;
|
||||||
|
authors:
|
||||||
|
| {
|
||||||
|
value: string;
|
||||||
|
relationTo: 'recorders';
|
||||||
|
}[]
|
||||||
|
| {
|
||||||
|
value: Recorder;
|
||||||
|
relationTo: 'recorders';
|
||||||
|
}[];
|
||||||
|
categories?:
|
||||||
|
| {
|
||||||
|
value: string;
|
||||||
|
relationTo: 'tags';
|
||||||
|
}[]
|
||||||
|
| {
|
||||||
|
value: Tag;
|
||||||
|
relationTo: 'tags';
|
||||||
|
}[];
|
||||||
|
translations: {
|
||||||
|
language: string | Language;
|
||||||
|
sourceLanguage: string | Language;
|
||||||
|
title: string;
|
||||||
|
summary?: string;
|
||||||
|
translators?: string[] | Recorder[];
|
||||||
|
proofreaders?: string[] | Recorder[];
|
||||||
|
content?: {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
id?: string;
|
||||||
|
}[];
|
||||||
|
publishedDate: string;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
_status?: 'draft' | 'published';
|
||||||
|
}
|
||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
resetPasswordToken?: string;
|
||||||
|
resetPasswordExpiration?: string;
|
||||||
|
salt?: string;
|
||||||
|
hash?: string;
|
||||||
|
loginAttempts?: number;
|
||||||
|
lockUntil?: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
export type AdminComponent<T extends Record<string, any>> = (props: {
|
|
||||||
data: Partial<T>;
|
|
||||||
index?: number;
|
|
||||||
}) => string;
|
|
|
@ -1,7 +1,11 @@
|
||||||
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 });
|
||||||
|
|
Loading…
Reference in New Issue