Spent the weekend improving stuff

This commit is contained in:
DrMint 2023-10-30 00:10:11 +01:00
parent 4f807c410b
commit cbd0251ad5
33 changed files with 1189 additions and 1120 deletions

2
.gitignore vendored
View File

@ -168,3 +168,5 @@ dist
# Ignore Data # Ignore Data
mongo/ mongo/
uploads/ uploads/
build
core

BIN
bun.lockb

Binary file not shown.

View File

@ -10,6 +10,7 @@ services:
- node_modules:/home/node/app/node_modules - node_modules:/home/node/app/node_modules
working_dir: /home/node/app/ working_dir: /home/node/app/
command: sh -c "npm install && npm run generate:types && npm run dev" command: sh -c "npm install && npm run generate:types && npm run dev"
# command: sh -c "npm install && npm run generate:types && npm run build:payload && npm run serve"
depends_on: depends_on:
- mongo - mongo
environment: environment:

37
package-lock.json generated
View File

@ -13,11 +13,10 @@
"@payloadcms/bundler-webpack": "^1.0.4", "@payloadcms/bundler-webpack": "^1.0.4",
"@payloadcms/db-mongodb": "^1.0.4", "@payloadcms/db-mongodb": "^1.0.4",
"@payloadcms/richtext-lexical": "^0.1.15", "@payloadcms/richtext-lexical": "^0.1.15",
"clean-deep": "^3.4.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"language-tags": "^1.0.9", "language-tags": "^1.0.9",
"luxon": "^3.4.3", "luxon": "^3.4.3",
"payload": "^2.0.12", "payload": "^2.0.13",
"styled-components": "^6.1.0" "styled-components": "^6.1.0"
}, },
"devDependencies": { "devDependencies": {
@ -5413,19 +5412,6 @@
"node": ">= 10.0" "node": ">= 10.0"
} }
}, },
"node_modules/clean-deep": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/clean-deep/-/clean-deep-3.4.0.tgz",
"integrity": "sha512-Lo78NV5ItJL/jl+B5w0BycAisaieJGXK1qYi/9m4SjR8zbqmrUtO7Yhro40wEShGmmxs/aJLI/A+jNhdkXK8mw==",
"dependencies": {
"lodash.isempty": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.transform": "^4.6.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/clean-stack": { "node_modules/clean-stack": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@ -9321,26 +9307,11 @@
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
}, },
"node_modules/lodash.isempty": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
"integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg=="
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"node_modules/lodash.memoize": { "node_modules/lodash.memoize": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="
}, },
"node_modules/lodash.transform": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz",
"integrity": "sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ=="
},
"node_modules/lodash.uniq": { "node_modules/lodash.uniq": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@ -11140,9 +11111,9 @@
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
}, },
"node_modules/payload": { "node_modules/payload": {
"version": "2.0.12", "version": "2.0.13",
"resolved": "https://registry.npmjs.org/payload/-/payload-2.0.12.tgz", "resolved": "https://registry.npmjs.org/payload/-/payload-2.0.13.tgz",
"integrity": "sha512-M3x9Y53ukiflZC4STri/34Gx4VjMk+UjuLc38dQRiCLPFGXEuqPfFFdt+c1Lh4ZeTQmMlWzzJcRMQ00tK+kWYg==", "integrity": "sha512-rD9ncVH8ClP7SphDymnrtVv0GAwHeyBXt9b1wSQBF15Dx/svU5rD1OEDtDPgEUTQApnySBVsB4NDGM1xO32YjA==",
"dependencies": { "dependencies": {
"@date-io/date-fns": "2.16.0", "@date-io/date-fns": "2.16.0",
"@dnd-kit/core": "6.0.8", "@dnd-kit/core": "6.0.8",

View File

@ -19,18 +19,18 @@
"precommit": "npm run generate:types && npm run prettier && npm run unused-exports && npm run tsc", "precommit": "npm run generate:types && npm run prettier && npm run unused-exports && npm run tsc",
"upgrade": "ncu", "upgrade": "ncu",
"clean": "sudo rm -r uploads mongo", "clean": "sudo rm -r uploads mongo",
"start": "sudo docker compose up" "start": "sudo docker compose up",
"stop": "sudo docker compose down"
}, },
"dependencies": { "dependencies": {
"@fontsource/vollkorn": "^5.0.17", "@fontsource/vollkorn": "^5.0.17",
"@payloadcms/bundler-webpack": "^1.0.4", "@payloadcms/bundler-webpack": "^1.0.4",
"@payloadcms/db-mongodb": "^1.0.4", "@payloadcms/db-mongodb": "^1.0.4",
"@payloadcms/richtext-lexical": "^0.1.15", "@payloadcms/richtext-lexical": "^0.1.15",
"clean-deep": "^3.4.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"language-tags": "^1.0.9", "language-tags": "^1.0.9",
"luxon": "^3.4.3", "luxon": "^3.4.3",
"payload": "^2.0.12", "payload": "^2.0.13",
"styled-components": "^6.1.0" "styled-components": "^6.1.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -2,6 +2,7 @@ import { CollectionConfig } from "payload/types";
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { CollectionGroups, Collections } from "../../constants"; import { CollectionGroups, Collections } from "../../constants";
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField"; import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
@ -43,25 +44,22 @@ export const ChronologyEras: CollectionConfig = buildCollectionConfig({
endpoints: [importFromStrapi, getAllEndpoint], endpoints: [importFromStrapi, getAllEndpoint],
fields: [ fields: [
slugField({ name: fields.slug }), slugField({ name: fields.slug }),
{ rowField([
type: "row",
fields: [
{ {
name: fields.startingYear, name: fields.startingYear,
type: "number", type: "number",
min: 0, min: 0,
required: true, required: true,
admin: { width: "0%", description: "The year the era started (year included)" }, admin: { description: "The year the era started (year included)" },
}, },
{ {
name: fields.endingYear, name: fields.endingYear,
type: "number", type: "number",
min: 0, min: 0,
required: true, required: true,
admin: { width: "0%", description: "The year the era ended (year included)" }, admin: { description: "The year the era ended (year included)" },
},
],
}, },
]),
translatedFields({ translatedFields({
name: fields.translations, name: fields.translations,
admin: { useAsTitle: fields.translationsTitle }, admin: { useAsTitle: fields.translationsTitle },

View File

@ -5,6 +5,7 @@ import {
publishStatusFilters, publishStatusFilters,
} from "../../components/QuickFilters"; } from "../../components/QuickFilters";
import { CollectionGroups, Collections } from "../../constants"; import { CollectionGroups, Collections } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { createEditor } from "../../utils/editor"; import { createEditor } from "../../utils/editor";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
@ -69,32 +70,26 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig(
name: fields.date, name: fields.date,
validate: validateDate, validate: validateDate,
fields: [ fields: [
{ rowField([
type: "row",
fields: [
{ {
name: fields.dateYear, name: fields.dateYear,
type: "number", type: "number",
required: true, required: true,
min: 0, min: 0,
admin: { width: "0%" },
}, },
{ {
name: fields.dateMonth, name: fields.dateMonth,
type: "number", type: "number",
min: 1, min: 1,
max: 12, max: 12,
admin: { width: "0%" },
}, },
{ {
name: fields.dateDay, name: fields.dateDay,
type: "number", type: "number",
min: 1, min: 1,
max: 31, max: 31,
admin: { width: "0%" },
},
],
}, },
]),
], ],
}, },
{ {

View File

@ -1,9 +1,11 @@
import { sectionBlock } from "../../blocks/sectionBlock"; import { sectionBlock } from "../../blocks/sectionBlock";
import { transcriptBlock } from "../../blocks/transcriptBlock"; import { transcriptBlock } from "../../blocks/transcriptBlock";
import { CollectionGroups, Collections, FileTypes, KeysTypes } from "../../constants"; import { CollectionGroups, Collections, FileTypes, KeysTypes } from "../../constants";
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { fileField } from "../../fields/fileField/fileField"; import { fileField } from "../../fields/fileField/fileField";
import { imageField } from "../../fields/imageField/imageField"; import { imageField } from "../../fields/imageField/imageField";
import { keysField } from "../../fields/keysField/keysField"; import { keysField } from "../../fields/keysField/keysField";
import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField"; import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
@ -13,6 +15,7 @@ import { isDefined } from "../../utils/asserts";
import { createEditor } from "../../utils/editor"; import { createEditor } from "../../utils/editor";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
import { importFromStrapi } from "./endpoints/importFromStrapi"; import { importFromStrapi } from "./endpoints/importFromStrapi";
import { importRelationsFromStrapi } from "./endpoints/importRelationsFromStrapi";
const fields = { const fields = {
slug: "slug", slug: "slug",
@ -35,6 +38,10 @@ const fields = {
audioNotes: "videoNotes", audioNotes: "videoNotes",
status: "status", status: "status",
updatedBy: "updatedBy", updatedBy: "updatedBy",
previousContents: "previousContents",
nextContents: "nextContents",
folders: "folders",
libraryItems: "libraryItems",
} as const satisfies Record<string, string>; } as const satisfies Record<string, string>;
export const Contents = buildVersionedCollectionConfig({ export const Contents = buildVersionedCollectionConfig({
@ -63,51 +70,44 @@ export const Contents = buildVersionedCollectionConfig({
beforeDuplicateAddCopyTo(fields.slug), beforeDuplicateAddCopyTo(fields.slug),
]), ]),
}, },
preview: (doc) => `https://accords-library.com/contents/${doc.slug}`,
}, },
endpoints: [importFromStrapi], endpoints: [importFromStrapi, importRelationsFromStrapi],
fields: [ fields: [
{ rowField([
type: "row", slugField({ name: fields.slug }),
fields: [
slugField({ name: fields.slug, admin: { width: "0%" } }),
imageField({ imageField({
name: fields.thumbnail, name: fields.thumbnail,
relationTo: Collections.ContentsThumbnails, relationTo: Collections.ContentsThumbnails,
admin: { width: "0%" },
}), }),
], ]),
}, rowField([
{
type: "row",
fields: [
keysField({ keysField({
name: fields.categories, name: fields.categories,
relationTo: KeysTypes.Categories, relationTo: KeysTypes.Categories,
hasMany: true, hasMany: true,
admin: { allowCreate: false, width: "0%" },
}), }),
keysField({ keysField({
name: fields.type, name: fields.type,
relationTo: KeysTypes.Contents, relationTo: KeysTypes.Contents,
admin: { allowCreate: false, width: "0%" },
}), }),
], backPropagationField({
}, name: fields.libraryItems,
hasMany: true,
relationTo: Collections.LibraryItems,
where: ({ id }) => ({ "contents.content": { equals: id } }),
}),
]),
translatedFields({ translatedFields({
name: fields.translations, name: fields.translations,
admin: { useAsTitle: fields.title, hasSourceLanguage: true }, admin: { useAsTitle: fields.title, hasSourceLanguage: true },
required: true, required: true,
minRows: 1, minRows: 1,
fields: [ fields: [
{ rowField([
type: "row",
fields: [
{ name: fields.pretitle, type: "text" }, { name: fields.pretitle, type: "text" },
{ name: fields.title, type: "text", required: true }, { name: fields.title, type: "text", required: true },
{ name: fields.subtitle, type: "text" }, { name: fields.subtitle, type: "text" },
], ]),
},
{ {
name: fields.summary, name: fields.summary,
type: "richText", type: "richText",
@ -137,9 +137,7 @@ export const Contents = buildVersionedCollectionConfig({
alignment: true, alignment: true,
}), }),
}, },
{ rowField([
type: "row",
fields: [
{ {
name: fields.textTranscribers, name: fields.textTranscribers,
label: "Transcribers", label: "Transcribers",
@ -149,7 +147,6 @@ export const Contents = buildVersionedCollectionConfig({
admin: { admin: {
condition: (_, siblingData) => condition: (_, siblingData) =>
siblingData.language === siblingData.sourceLanguage, siblingData.language === siblingData.sourceLanguage,
width: "0%",
}, },
}, },
{ {
@ -161,7 +158,6 @@ export const Contents = buildVersionedCollectionConfig({
admin: { admin: {
condition: (_, siblingData) => condition: (_, siblingData) =>
siblingData.language !== siblingData.sourceLanguage, siblingData.language !== siblingData.sourceLanguage,
width: "0%",
}, },
}, },
{ {
@ -170,10 +166,8 @@ export const Contents = buildVersionedCollectionConfig({
type: "relationship", type: "relationship",
relationTo: Collections.Recorders, relationTo: Collections.Recorders,
hasMany: true, hasMany: true,
admin: { width: "0%" },
},
],
}, },
]),
{ {
name: fields.textNotes, name: fields.textNotes,
label: "Notes", label: "Notes",
@ -185,50 +179,63 @@ export const Contents = buildVersionedCollectionConfig({
{ {
label: "Video", label: "Video",
fields: [ fields: [
{ rowField([
type: "row",
fields: [
fileField({ fileField({
name: fields.video, name: fields.video,
relationTo: FileTypes.ContentVideo, relationTo: FileTypes.ContentVideo,
admin: { width: "0%" },
}), }),
{ {
name: fields.videoNotes, name: fields.videoNotes,
label: "Notes", label: "Notes",
type: "richText", type: "richText",
editor: createEditor({ inlines: true, lists: true, links: true }), editor: createEditor({ inlines: true, lists: true, links: true }),
admin: { width: "0%" },
},
],
}, },
]),
], ],
}, },
{ {
label: "Audio", label: "Audio",
fields: [ fields: [
{ rowField([
type: "row",
fields: [
fileField({ fileField({
name: fields.audio, name: fields.audio,
relationTo: FileTypes.ContentAudio, relationTo: FileTypes.ContentAudio,
admin: { width: "0%" },
}), }),
{ {
name: fields.audioNotes, name: fields.audioNotes,
label: "Notes", label: "Notes",
type: "richText", type: "richText",
editor: createEditor({ inlines: true, lists: true, links: true }), editor: createEditor({ inlines: true, lists: true, links: true }),
admin: { width: "0%" },
},
],
}, },
]),
], ],
}, },
], ],
}, },
], ],
}), }),
rowField([
backPropagationField({
name: fields.folders,
hasMany: true,
relationTo: Collections.ContentsFolders,
where: ({ id }) => ({ contents: { equals: id } }),
admin: {
description: `You can set the folder(s) from the "Contents Folders" collection`,
},
}),
backPropagationField({
name: fields.previousContents,
relationTo: Collections.Contents,
hasMany: true,
where: ({ id }) => ({ [fields.nextContents]: { equals: id } }),
}),
{
name: fields.nextContents,
type: "relationship",
hasMany: true,
relationTo: Collections.Contents,
},
]),
], ],
}); });

View File

@ -60,22 +60,17 @@ export const importFromStrapi = createStrapiImportEndpoint<StrapiContent>({
image: thumbnail, image: thumbnail,
}); });
const data: MarkOptional<Content, "createdAt" | "id" | "updatedAt" | "updatedBy"> = { const handleTranslation = async ({
slug, language,
categories: title,
categories.data && description,
(await Promise.all( pre_title,
categories.data.map(async (category) => await findCategory(category.attributes.slug)) subtitle,
)), text_set,
type: type.data && (await findContentType(type.data?.attributes.slug)), }: StrapiContent["translations"][number]) => {
thumbnail: thumbnailId,
translations: await Promise.all(
translations.map(
async ({ language, title, description, pre_title, subtitle, text_set }) => {
if (isUndefined(language.data)) if (isUndefined(language.data))
throw new Error("A language is required for a content translation"); throw new Error("A language is required for a content translation");
if (isUndefined(text_set)) if (isUndefined(text_set)) throw new Error("Only content with text_set are supported");
throw new Error("Only content with text_set are supported");
if (isUndefined(text_set.source_language.data)) if (isUndefined(text_set.source_language.data))
throw new Error("A language is required for a content translation text_set"); throw new Error("A language is required for a content translation text_set");
return { return {
@ -86,9 +81,7 @@ export const importFromStrapi = createStrapiImportEndpoint<StrapiContent>({
subtitle, subtitle,
summary: isNotEmpty(description) ? plainTextToLexical(description) : undefined, summary: isNotEmpty(description) ? plainTextToLexical(description) : undefined,
textContent: plainTextToLexical(text_set.text), textContent: plainTextToLexical(text_set.text),
textNotes: isNotEmpty(text_set.notes) textNotes: isNotEmpty(text_set.notes) ? plainTextToLexical(text_set.notes) : undefined,
? plainTextToLexical(text_set.notes)
: undefined,
textTranscribers: textTranscribers:
text_set.transcribers.data && text_set.transcribers.data &&
(await Promise.all( (await Promise.all(
@ -111,9 +104,18 @@ export const importFromStrapi = createStrapiImportEndpoint<StrapiContent>({
) )
)), )),
}; };
} };
)
), const data: MarkOptional<Content, "createdAt" | "id" | "updatedAt" | "updatedBy"> = {
slug,
categories:
categories.data &&
(await Promise.all(
categories.data.map(async (category) => await findCategory(category.attributes.slug))
)),
type: type.data && (await findContentType(type.data?.attributes.slug)),
thumbnail: thumbnailId,
translations: await Promise.all(translations.map(handleTranslation)),
}; };
return data; return data;
}, },

View File

@ -0,0 +1,38 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { findContent } from "../../../utils/localApi";
type StrapiContent = {
slug: string;
next_contents: { data: { attributes: { slug: string } }[] };
};
export const importRelationsFromStrapi = createStrapiImportEndpoint<StrapiContent>({
strapi: {
collection: "contents",
params: {
populate: ["next_contents"],
},
},
payload: {
path: "/strapi/related-content",
collection: Collections.Contents,
import: async ({ slug, next_contents }, user) => {
if (next_contents.data.length === 0) return;
const currentContent = await findContent(slug);
const nextContents: string[] = [];
for (const nextContent of next_contents.data) {
const result = await findContent(nextContent.attributes.slug);
nextContents.push(result);
}
payload.update({
collection: Collections.Contents,
id: currentContent,
data: { nextContents },
user,
});
},
},
});

View File

@ -1,4 +1,5 @@
import { CollectionGroups, Collections } from "../../constants"; import { CollectionGroups, Collections } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField"; import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
@ -37,24 +38,19 @@ export const ContentsFolders = buildCollectionConfig({
}, },
fields: [{ name: fields.name, type: "text", required: true }], fields: [{ name: fields.name, type: "text", required: true }],
}), }),
{ rowField([
type: "row",
fields: [
{ {
type: "relationship", type: "relationship",
name: fields.subfolders, name: fields.subfolders,
relationTo: Collections.ContentsFolders, relationTo: Collections.ContentsFolders,
hasMany: true, hasMany: true,
admin: { width: "0%" },
}, },
{ {
type: "relationship", type: "relationship",
name: fields.contents, name: fields.contents,
relationTo: Collections.Contents, relationTo: Collections.Contents,
hasMany: true, hasMany: true,
admin: { width: "0%" },
},
],
}, },
]),
], ],
}); });

View File

@ -4,6 +4,7 @@ import { Collections } from "../../../constants";
import { CollectionEndpoint } from "../../../types/payload"; import { CollectionEndpoint } from "../../../types/payload";
import { StrapiLanguage } from "../../../types/strapi"; import { StrapiLanguage } from "../../../types/strapi";
import { isUndefined } from "../../../utils/asserts"; import { isUndefined } from "../../../utils/asserts";
import { findContent } from "../../../utils/localApi";
type StrapiContentsFolder = { type StrapiContentsFolder = {
id: string; id: string;
@ -11,13 +12,14 @@ type StrapiContentsFolder = {
slug: string; slug: string;
titles?: { title: string; language: StrapiLanguage }[]; titles?: { title: string; language: StrapiLanguage }[];
subfolders: { data: StrapiContentsFolder[] }; subfolders: { data: StrapiContentsFolder[] };
contents: { data: { id: number }[] }; contents: { data: { attributes: { slug: string } }[] };
}; };
}; };
const getStrapiContentFolder = async (id: number): Promise<StrapiContentsFolder> => { const getStrapiContentFolder = async (id: number): Promise<StrapiContentsFolder> => {
const paramsWithPagination = QueryString.stringify({ const paramsWithPagination = QueryString.stringify({
populate: [ populate: [
"contents",
"subfolders", "subfolders",
"subfolders.contents", "subfolders.contents",
"subfolders.titles", "subfolders.titles",
@ -72,17 +74,31 @@ export const importFromStrapi: CollectionEndpoint = {
} }
let foldersCreated = 0; let foldersCreated = 0;
const errors: string[] = [];
const createContentFolder = async (data: StrapiContentsFolder): Promise<string> => { const createContentFolder = async (data: StrapiContentsFolder): Promise<string> => {
const { slug, titles } = data.attributes;
const subfolders = await Promise.all( const subfolders = await Promise.all(
data.attributes.subfolders.data.map(createContentFolder) data.attributes.subfolders.data.map(createContentFolder)
); );
const { slug, titles } = data.attributes;
const contents: string[] = [];
for (const content of data.attributes.contents.data) {
try {
const result = await findContent(content.attributes.slug);
contents.push(result);
} catch (e) {
errors.push(`Couldn't add ${content.attributes.slug} to folder ${slug}`);
}
}
const result = await payload.create({ const result = await payload.create({
collection: Collections.ContentsFolders, collection: Collections.ContentsFolders,
data: { data: {
slug, slug,
subfolders, subfolders,
contents,
translations: titles?.map(({ title, language }) => { translations: titles?.map(({ title, language }) => {
if (isUndefined(language.data)) if (isUndefined(language.data))
throw new Error("A language is required for a content folder translation"); throw new Error("A language is required for a content folder translation");
@ -102,6 +118,8 @@ export const importFromStrapi: CollectionEndpoint = {
res.status(500).json({ message: "Something went wrong", error: e }); res.status(500).json({ message: "Something went wrong", error: e });
} }
res.status(200).json({ message: `${foldersCreated} entries have been added successfully.` }); res
.status(200)
.json({ message: `${foldersCreated} entries have been added successfully.`, errors });
}, },
}; };

View File

@ -2,6 +2,7 @@ import payload from "payload";
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { QuickFilters } from "../../components/QuickFilters"; import { QuickFilters } from "../../components/QuickFilters";
import { CollectionGroups, Collections, KeysTypes, LanguageCodes } from "../../constants"; import { CollectionGroups, Collections, KeysTypes, LanguageCodes } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
import { Key } from "../../types/collections"; import { Key } from "../../types/collections";
@ -95,14 +96,11 @@ export const Keys = buildCollectionConfig({
useAsTitle: fields.translationsName, useAsTitle: fields.translationsName,
}, },
fields: [ fields: [
{ rowField([
type: "row",
fields: [
{ {
name: fields.translationsName, name: fields.translationsName,
type: "text", type: "text",
required: true, required: true,
admin: { width: "0%" },
}, },
{ {
name: fields.translationsShort, name: fields.translationsShort,
@ -110,11 +108,9 @@ export const Keys = buildCollectionConfig({
admin: { admin: {
condition: (data: Partial<Key>) => condition: (data: Partial<Key>) =>
isDefined(data.type) && keysTypesWithShort.includes(data.type), isDefined(data.type) && keysTypesWithShort.includes(data.type),
width: "0%",
}, },
}, },
], ]),
},
], ],
}), }),
], ],

View File

@ -1,4 +1,6 @@
import { CollectionGroups, Collections } from "../../constants"; import { CollectionGroups, Collections } from "../../constants";
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField"; import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
@ -11,6 +13,7 @@ const fields = {
description: "description", description: "description",
subfolders: "subfolders", subfolders: "subfolders",
items: "items", items: "items",
parentFolders: "parentFolders",
} as const satisfies Record<string, string>; } as const satisfies Record<string, string>;
export const LibraryFolders = buildCollectionConfig({ export const LibraryFolders = buildCollectionConfig({
@ -44,24 +47,25 @@ export const LibraryFolders = buildCollectionConfig({
}, },
], ],
}), }),
{ rowField([
type: "row", backPropagationField({
fields: [ name: fields.parentFolders,
relationTo: Collections.LibraryFolders,
hasMany: true,
where: ({ id }) => ({ [fields.subfolders]: { equals: id } }),
}),
{ {
type: "relationship", type: "relationship",
name: fields.subfolders, name: fields.subfolders,
relationTo: Collections.LibraryFolders, relationTo: Collections.LibraryFolders,
hasMany: true, hasMany: true,
admin: { width: "0%" },
}, },
{ {
type: "relationship", type: "relationship",
name: fields.items, name: fields.items,
relationTo: Collections.LibraryItems, relationTo: Collections.LibraryItems,
hasMany: true, hasMany: true,
admin: { width: "0%" },
},
],
}, },
]),
], ],
}); });

View File

@ -8,10 +8,12 @@ import {
LibraryItemsTextualPageOrders, LibraryItemsTextualPageOrders,
LibraryItemsTypes, LibraryItemsTypes,
} from "../../constants"; } from "../../constants";
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { componentField } from "../../fields/componentField/componentField";
import { fileField } from "../../fields/fileField/fileField"; import { fileField } from "../../fields/fileField/fileField";
import { imageField } from "../../fields/imageField/imageField"; import { imageField } from "../../fields/imageField/imageField";
import { keysField } from "../../fields/keysField/keysField"; import { keysField } from "../../fields/keysField/keysField";
import { optionalGroupField } from "../../fields/optionalGroupField/optionalGroupField"; import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField"; import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
@ -25,6 +27,8 @@ import { RowLabel } from "./components/RowLabel";
const fields = { const fields = {
status: "_status", status: "_status",
itemType: "itemType",
language: "language",
slug: "slug", slug: "slug",
thumbnail: "thumbnail", thumbnail: "thumbnail",
@ -36,10 +40,7 @@ const fields = {
translations: "translations", translations: "translations",
translationsDescription: "description", translationsDescription: "description",
rootItem: "rootItem",
primary: "primary",
digital: "digital", digital: "digital",
downloadable: "downloadable",
size: "size", size: "size",
width: "width", width: "width",
@ -59,13 +60,11 @@ const fields = {
categories: "categories", categories: "categories",
itemType: "itemType",
textual: "textual", textual: "textual",
textualSubtype: "subtype", textualSubtype: "subtype",
textualBindingType: "bindingType", textualBindingType: "bindingType",
textualPageCount: "pageCount", textualPageCount: "pageCount",
textualPageOrder: "pageOrder", textualPageOrder: "pageOrder",
textualLanguages: "languages",
audio: "audio", audio: "audio",
audioSubtype: "audioSubtype", audioSubtype: "audioSubtype",
@ -73,7 +72,20 @@ const fields = {
audioTracksFile: "file", audioTracksFile: "file",
audioTracksTitle: "title", audioTracksTitle: "title",
video: "video",
videoSubtype: "subtype",
game: "game",
gameDemo: "demo",
gamePlatform: "platform",
gameAudioLanguages: "audioLanguages",
gameSubtitleLanguages: "subtitleLanguages",
gameInterfacesLanguages: "interfacesLanguages",
scans: "scans", scans: "scans",
scansScanners: "scanners",
scansCleaners: "cleaners",
scansTypesetters: "typesetters",
scansCover: "cover", scansCover: "cover",
scansCoverFlapFront: "flapFront", scansCoverFlapFront: "flapFront",
@ -114,6 +126,8 @@ const fields = {
scansPagesPage: "page", scansPagesPage: "page",
scansPagesImage: "image", scansPagesImage: "image",
scanArchiveFile: "archiveFile",
contents: "contents", contents: "contents",
contentsContent: "content", contentsContent: "content",
contentsPageStart: "pageStart", contentsPageStart: "pageStart",
@ -121,6 +135,10 @@ const fields = {
contentsTimeStart: "timeStart", contentsTimeStart: "timeStart",
contentsTimeEnd: "timeEnd", contentsTimeEnd: "timeEnd",
contentsNote: "note", contentsNote: "note",
parentFolders: "parentFolders",
parentItems: "parentItems",
subitems: "subitems",
} as const satisfies Record<string, string>; } as const satisfies Record<string, string>;
export const LibraryItems = buildVersionedCollectionConfig({ export const LibraryItems = buildVersionedCollectionConfig({
@ -143,12 +161,9 @@ export const LibraryItems = buildVersionedCollectionConfig({
beforeDuplicateAddCopyTo(fields.slug), beforeDuplicateAddCopyTo(fields.slug),
]), ]),
}, },
preview: (doc) => `https://accords-library.com/library/${doc.slug}`,
}, },
fields: [ fields: [
{ rowField([
type: "row",
fields: [
{ {
name: fields.itemType, name: fields.itemType,
type: "radio", type: "radio",
@ -158,22 +173,20 @@ export const LibraryItems = buildVersionedCollectionConfig({
})), })),
admin: { admin: {
layout: "horizontal", layout: "horizontal",
width: "0%",
}, },
}, },
{ {
name: fields.digital, name: fields.language,
type: "checkbox", type: "relationship",
relationTo: Collections.Languages,
required: true, required: true,
defaultValue: false,
admin: { admin: {
allowCreate: false,
description: description:
"The item is the digital version of another item, or the item is sold only digitally.", "This item sole or primary language (most notably, the language used on the cover)",
width: "0%",
}, },
}, },
], ]),
},
{ {
type: "tabs", type: "tabs",
admin: { admin: {
@ -183,9 +196,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
{ {
label: "Overview", label: "Overview",
fields: [ fields: [
{ rowField([
type: "row",
fields: [
slugField({ slugField({
name: fields.slug, name: fields.slug,
}), }),
@ -193,44 +204,24 @@ export const LibraryItems = buildVersionedCollectionConfig({
name: fields.thumbnail, name: fields.thumbnail,
relationTo: Collections.LibraryItemsThumbnails, relationTo: Collections.LibraryItemsThumbnails,
}), }),
], ]),
}, rowField([
{
type: "row",
fields: [
{ name: fields.pretitle, type: "text" }, { name: fields.pretitle, type: "text" },
{ name: fields.title, type: "text", required: true }, { name: fields.title, type: "text", required: true },
{ name: fields.subtitle, type: "text" }, { name: fields.subtitle, type: "text" },
], ]),
},
{ {
type: "row", name: fields.digital,
fields: [
{
name: fields.rootItem,
type: "checkbox", type: "checkbox",
required: true, required: true,
defaultValue: true, defaultValue: false,
admin: {
description: "Only items that can be sold separetely should be root items.",
width: "0%",
},
},
{
name: fields.primary,
type: "checkbox",
required: true,
defaultValue: true,
admin: { admin: {
description: description:
"A primary item is an official item that focuses primarly on one or more of our Categories.", "The item is the digital version of another item, or the item is sold only digitally.",
width: "0%",
}, },
}, },
], ],
}, },
],
},
{ {
label: "Images", label: "Images",
fields: [ fields: [
@ -241,6 +232,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
description: description:
"Additional images of the item (unboxing, on shelf, promotional images...)", "Additional images of the item (unboxing, on shelf, promotional images...)",
}, },
labels: { singular: "Image", plural: "Images" },
fields: [ fields: [
imageField({ imageField({
name: fields.galleryImage, name: fields.galleryImage,
@ -248,226 +240,190 @@ export const LibraryItems = buildVersionedCollectionConfig({
}), }),
], ],
}, },
optionalGroupField({ componentField({
name: fields.scans, name: fields.scans,
fields: [ fields: [
optionalGroupField({ rowField([
{
name: fields.scansScanners,
type: "relationship",
relationTo: Collections.Recorders,
hasMany: true,
required: true,
},
{
name: fields.scansCleaners,
type: "relationship",
relationTo: Collections.Recorders,
hasMany: true,
required: true,
},
{
name: fields.scansTypesetters,
type: "relationship",
relationTo: Collections.Recorders,
hasMany: true,
},
]),
componentField({
name: fields.scansCover, name: fields.scansCover,
fields: [ fields: [
{ rowField([
type: "row",
fields: [
imageField({ imageField({
name: fields.scansCoverFront, name: fields.scansCoverFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansCoverSpine, name: fields.scansCoverSpine,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansCoverBack, name: fields.scansCoverBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
], ]),
}, rowField([
{
type: "row",
fields: [
imageField({ imageField({
name: fields.scansCoverInsideFront, name: fields.scansCoverInsideFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansCoverBack, name: fields.scansCoverBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
], ]),
}, rowField([
{
type: "row",
fields: [
imageField({ imageField({
name: fields.scansCoverFlapFront, name: fields.scansCoverFlapFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansCoverFlapBack, name: fields.scansCoverFlapBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansCoverInsideFlapFront, name: fields.scansCoverInsideFlapFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansCoverInsideFlapBack, name: fields.scansCoverInsideFlapBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
], ]),
},
], ],
}), }),
optionalGroupField({ componentField({
name: fields.scansDustjacket, name: fields.scansDustjacket,
label: "Dust Jacket", label: "Dust Jacket",
labels: { singular: "Dust Jacket", plural: "Dust Jackets" },
admin: { admin: {
description: description:
"The dust jacket of a book is the detachable outer cover with folded \ "The dust jacket of a book is the detachable outer cover with folded \
flaps that hold it to the front and back book covers", flaps that hold it to the front and back book covers",
}, },
fields: [ fields: [
{ rowField([
type: "row",
fields: [
imageField({ imageField({
name: fields.scansDustjacketFront, name: fields.scansDustjacketFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansDustjacketSpine, name: fields.scansDustjacketSpine,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansDustjacketBack, name: fields.scansDustjacketBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
], ]),
}, rowField([
{
type: "row",
fields: [
imageField({ imageField({
name: fields.scansDustjacketInsideFront, name: fields.scansDustjacketInsideFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansDustjacketInsideSpine, name: fields.scansDustjacketInsideSpine,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansDustjacketInsideBack, name: fields.scansDustjacketInsideBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
], ]),
}, rowField([
{
type: "row",
fields: [
imageField({ imageField({
name: fields.scansDustjacketFlapFront, name: fields.scansDustjacketFlapFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansDustjacketFlapBack, name: fields.scansDustjacketFlapBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansDustjacketInsideFlapFront, name: fields.scansDustjacketInsideFlapFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansDustjacketInsideFlapBack, name: fields.scansDustjacketInsideFlapBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
], ]),
},
], ],
}), }),
optionalGroupField({ componentField({
name: fields.scansObi, name: fields.scansObi,
label: "Obi", label: "Obi",
labels: { singular: "Obi Belt", plural: "Obi Belts" },
admin: { admin: {
description: description:
"An obi is a strip of paper looped around a book or other product. \ "An obi is a strip of paper looped around a book or other product. \
it typically add marketing claims, or other relevant information about the product.", it typically add marketing claims, or other relevant information about the product.",
}, },
fields: [ fields: [
{ rowField([
type: "row",
fields: [
imageField({ imageField({
name: fields.scansObiFront, name: fields.scansObiFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansObiSpine, name: fields.scansObiSpine,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansObiBack, name: fields.scansObiBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
], ]),
}, rowField([
{
type: "row",
fields: [
imageField({ imageField({
name: fields.scansObiInsideFront, name: fields.scansObiInsideFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansObiInsideSpine, name: fields.scansObiInsideSpine,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansObiInsideBack, name: fields.scansObiInsideBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
], ]),
}, rowField([
{
type: "row",
fields: [
imageField({ imageField({
name: fields.scansObiFlapFront, name: fields.scansObiFlapFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansObiFlapBack, name: fields.scansObiFlapBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansObiInsideFlapFront, name: fields.scansObiInsideFlapFront,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
imageField({ imageField({
name: fields.scansObiInsideFlapBack, name: fields.scansObiInsideFlapBack,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
admin: { width: "0%" },
}), }),
], ]),
},
], ],
}), }),
{ {
@ -483,35 +439,24 @@ export const LibraryItems = buildVersionedCollectionConfig({
}, },
}, },
fields: [ fields: [
{ rowField([
type: "row",
fields: [
{ {
name: fields.scansPagesPage, name: fields.scansPagesPage,
type: "number", type: "number",
required: true, required: true,
admin: { width: "0%" },
}, },
imageField({ imageField({
name: fields.scansPagesImage, name: fields.scansPagesImage,
relationTo: Collections.LibraryItemsScans, relationTo: Collections.LibraryItemsScans,
required: true, required: true,
admin: { width: "0%" },
}), }),
]),
], ],
}, },
], fileField({
}, name: fields.scanArchiveFile,
{ relationTo: FileTypes.LibraryScans,
name: fields.downloadable, }),
type: "checkbox",
required: true,
defaultValue: false,
admin: {
description: "Are the scans available for download?",
width: "0%",
},
},
], ],
}), }),
], ],
@ -535,27 +480,15 @@ export const LibraryItems = buildVersionedCollectionConfig({
keysField({ keysField({
name: fields.textualSubtype, name: fields.textualSubtype,
relationTo: KeysTypes.LibraryTextual, relationTo: KeysTypes.LibraryTextual,
hasMany: true,
admin: { allowCreate: false, width: "0%" },
}), }),
{
name: fields.textualLanguages,
type: "relationship",
relationTo: Collections.Languages,
hasMany: true,
admin: { allowCreate: false, width: "0%" },
},
],
},
{
type: "row",
fields: [
{ {
name: fields.textualPageCount, name: fields.textualPageCount,
type: "number", type: "number",
min: 1, min: 1,
admin: { width: "0%" },
}, },
],
},
rowField([
{ {
name: fields.textualBindingType, name: fields.textualBindingType,
type: "radio", type: "radio",
@ -568,7 +501,6 @@ export const LibraryItems = buildVersionedCollectionConfig({
admin: { admin: {
condition: (data: Partial<LibraryItem>) => !data.digital, condition: (data: Partial<LibraryItem>) => !data.digital,
layout: "horizontal", layout: "horizontal",
width: "0%",
}, },
}, },
{ {
@ -582,11 +514,9 @@ export const LibraryItems = buildVersionedCollectionConfig({
), ),
admin: { admin: {
layout: "horizontal", layout: "horizontal",
width: "0%",
}, },
}, },
], ]),
},
], ],
}, },
{ {
@ -597,41 +527,85 @@ export const LibraryItems = buildVersionedCollectionConfig({
condition: (data: Partial<LibraryItem>) => condition: (data: Partial<LibraryItem>) =>
data.itemType === LibraryItemsTypes.Audio, data.itemType === LibraryItemsTypes.Audio,
}, },
fields: [
{
type: "row",
fields: [ fields: [
keysField({ keysField({
name: fields.audioSubtype, name: fields.audioSubtype,
relationTo: KeysTypes.LibraryAudio, relationTo: KeysTypes.LibraryAudio,
hasMany: true,
admin: { allowCreate: false, width: "0%" },
}), }),
],
},
{ {
name: fields.audioTracks, name: fields.audioTracks,
type: "array", type: "array",
fields: [ fields: [
{ rowField([
type: "row",
fields: [
{ {
name: fields.audioTracksTitle, name: fields.audioTracksTitle,
type: "text", type: "text",
required: true, required: true,
admin: { width: "0%" },
}, },
fileField({ fileField({
name: fields.audioTracksFile, name: fields.audioTracksFile,
relationTo: FileTypes.LibrarySoundtracks, relationTo: FileTypes.LibrarySoundtracks,
required: true, required: true,
admin: { width: "0%" }, }),
]),
],
},
],
},
{
name: fields.video,
type: "group",
label: false,
admin: {
condition: (data: Partial<LibraryItem>) =>
data.itemType === LibraryItemsTypes.Video,
},
fields: [
keysField({
name: fields.videoSubtype,
relationTo: KeysTypes.LibraryVideo,
}), }),
], ],
}, },
], {
name: fields.game,
type: "group",
label: false,
admin: {
condition: (data: Partial<LibraryItem>) => data.itemType === LibraryItemsTypes.Game,
}, },
fields: [
rowField([
{
name: fields.gameDemo,
type: "checkbox",
admin: { description: "Is this item a demo for the game" },
},
keysField({
name: fields.gamePlatform,
relationTo: KeysTypes.GamePlatforms,
}),
]),
rowField([
{
name: fields.gameAudioLanguages,
type: "relationship",
relationTo: Collections.Languages,
hasMany: true,
},
{
name: fields.gameSubtitleLanguages,
type: "relationship",
relationTo: Collections.Languages,
hasMany: true,
},
{
name: fields.gameInterfacesLanguages,
type: "relationship",
relationTo: Collections.Languages,
hasMany: true,
},
]),
], ],
}, },
], ],
@ -639,25 +613,69 @@ export const LibraryItems = buildVersionedCollectionConfig({
{ {
label: "Details", label: "Details",
fields: [ fields: [
{ rowField([
type: "row",
fields: [
{ {
name: fields.releaseDate, name: fields.releaseDate,
type: "date", type: "date",
admin: { admin: {
date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" }, date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" },
width: "0%",
}, },
}, },
keysField({ keysField({
name: fields.categories, name: fields.categories,
relationTo: KeysTypes.Categories, relationTo: KeysTypes.Categories,
hasMany: true, hasMany: true,
admin: { allowCreate: false, width: "0%" },
}), }),
], ]),
componentField({
name: fields.size,
admin: {
condition: (data: Partial<LibraryItem>) => !data.digital,
description: "Add physical size information about the item",
}, },
fields: [
rowField([
{
name: fields.width,
type: "number",
required: true,
admin: { step: 1, description: "in mm." },
},
{
name: fields.height,
type: "number",
required: true,
admin: { step: 1, description: "in mm." },
},
{
name: fields.thickness,
type: "number",
admin: { step: 1, description: "in mm." },
},
]),
],
}),
componentField({
name: fields.price,
admin: { description: "Add pricing information about the item" },
fields: [
rowField([
{
name: fields.priceAmount,
type: "number",
required: true,
min: 0,
},
{
name: fields.priceCurrency,
type: "relationship",
relationTo: Collections.Currencies,
required: true,
admin: { allowCreate: false },
},
]),
],
}),
translatedFields({ translatedFields({
name: fields.translations, name: fields.translations,
label: "Descriptions", label: "Descriptions",
@ -671,66 +689,12 @@ export const LibraryItems = buildVersionedCollectionConfig({
}, },
], ],
}), }),
optionalGroupField({
name: fields.size,
admin: { condition: (data: Partial<LibraryItem>) => !data.digital },
fields: [
{
type: "row",
fields: [
{
name: fields.width,
type: "number",
required: true,
admin: { step: 1, width: "0%", description: "in mm." },
},
{
name: fields.height,
type: "number",
required: true,
admin: { step: 1, width: "0%", description: "in mm." },
},
{
name: fields.thickness,
type: "number",
admin: { step: 1, width: "0%", description: "in mm." },
},
],
},
],
}),
optionalGroupField({
name: fields.price,
admin: { className: "group-array", width: "0%" },
fields: [
{
type: "row",
fields: [
{
name: fields.priceAmount,
type: "number",
required: true,
min: 0,
admin: { width: "0%" },
},
{
name: fields.priceCurrency,
type: "relationship",
relationTo: Collections.Currencies,
required: true,
admin: { allowCreate: false, width: "0%" },
},
],
},
],
}),
{ {
name: fields.urls, name: fields.urls,
label: "URLs", label: "URLs",
type: "array", type: "array",
admin: { admin: {
description: "Links to official websites where to get/buy the item.", description: "Links to official websites where to get/buy the item.",
width: "0%",
}, },
fields: [{ name: fields.urlsUrl, type: "text", required: true }], fields: [{ name: fields.urlsUrl, type: "text", required: true }],
}, },
@ -739,6 +703,29 @@ export const LibraryItems = buildVersionedCollectionConfig({
{ {
label: "Contents", label: "Contents",
fields: [ fields: [
rowField([
backPropagationField({
name: fields.parentFolders,
relationTo: Collections.LibraryFolders,
hasMany: true,
where: ({ id }) => ({ items: { equals: id } }),
admin: {
description: `You can set the folders from the "Library Folders" collection`,
},
}),
backPropagationField({
name: fields.parentItems,
relationTo: Collections.LibraryItems,
hasMany: true,
where: ({ id }) => ({ [fields.subitems]: { equals: id } }),
}),
{
name: fields.subitems,
type: "relationship",
hasMany: true,
relationTo: Collections.LibraryItems,
},
]),
{ {
name: fields.contents, name: fields.contents,
type: "array", type: "array",
@ -752,7 +739,9 @@ export const LibraryItems = buildVersionedCollectionConfig({
{ {
type: "row", type: "row",
admin: { admin: {
condition: ({ itemType }) => itemType === LibraryItemsTypes.Textual, // TODO: Check why those condition doesn't work
condition: ({ itemType }: Partial<LibraryItem>) =>
itemType === LibraryItemsTypes.Textual,
}, },
fields: [ fields: [
{ {
@ -765,7 +754,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
{ {
type: "row", type: "row",
admin: { admin: {
condition: ({ itemType }) => condition: ({ itemType }: Partial<LibraryItem>) =>
itemType === LibraryItemsTypes.Audio || itemType === LibraryItemsTypes.Video, itemType === LibraryItemsTypes.Audio || itemType === LibraryItemsTypes.Video,
}, },
fields: [ fields: [
@ -781,7 +770,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
type: "richText", type: "richText",
editor: createEditor({ inlines: true, lists: true, links: true }), editor: createEditor({ inlines: true, lists: true, links: true }),
admin: { admin: {
condition: ({ itemType }) => condition: ({ itemType }: Partial<LibraryItem>) =>
itemType === LibraryItemsTypes.Game || itemType === LibraryItemsTypes.Other, itemType === LibraryItemsTypes.Game || itemType === LibraryItemsTypes.Other,
}, },
}, },

View File

@ -1,6 +1,9 @@
import { sectionBlock } from "../../blocks/sectionBlock";
import { QuickFilters, publishStatusFilters } from "../../components/QuickFilters"; import { QuickFilters, publishStatusFilters } from "../../components/QuickFilters";
import { CollectionGroups, Collections, KeysTypes } from "../../constants"; import { CollectionGroups, Collections, KeysTypes } from "../../constants";
import { imageField } from "../../fields/imageField/imageField"; import { imageField } from "../../fields/imageField/imageField";
import { keysField } from "../../fields/keysField/keysField";
import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField"; import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
@ -58,20 +61,14 @@ export const Posts = buildVersionedCollectionConfig({
preview: (doc) => `https://accords-library.com/news/${doc.slug}`, preview: (doc) => `https://accords-library.com/news/${doc.slug}`,
}, },
fields: [ fields: [
{ rowField([
type: "row", slugField({ name: fields.slug }),
fields: [
slugField({ name: fields.slug, admin: { width: "0%" } }),
imageField({ imageField({
name: fields.thumbnail, name: fields.thumbnail,
relationTo: Collections.PostsThumbnails, relationTo: Collections.PostsThumbnails,
admin: { width: "0%" },
}), }),
], ]),
}, rowField([
{
type: "row",
fields: [
{ {
name: fields.authors, name: fields.authors,
type: "relationship", type: "relationship",
@ -79,18 +76,9 @@ export const Posts = buildVersionedCollectionConfig({
required: true, required: true,
minRows: 1, minRows: 1,
hasMany: true, hasMany: true,
admin: { width: "0%" },
},
{
name: fields.categories,
type: "relationship",
relationTo: [Collections.Keys],
filterOptions: { type: { equals: KeysTypes.Categories } },
hasMany: true,
admin: { allowCreate: false, width: "0%" },
},
],
}, },
keysField({ name: fields.categories, relationTo: KeysTypes.Categories, hasMany: true }),
]),
translatedFields({ translatedFields({
name: fields.translations, name: fields.translations,
admin: { useAsTitle: fields.title, hasSourceLanguage: true }, admin: { useAsTitle: fields.title, hasSourceLanguage: true },
@ -104,8 +92,18 @@ export const Posts = buildVersionedCollectionConfig({
editor: createEditor({ inlines: true, lists: true, links: true }), editor: createEditor({ inlines: true, lists: true, links: true }),
}, },
{ {
type: "row", name: fields.content,
fields: [ type: "richText",
editor: createEditor({
images: true,
inlines: true,
alignment: true,
blocks: [sectionBlock],
links: true,
lists: true,
}),
},
rowField([
{ {
name: fields.translators, name: fields.translators,
type: "relationship", type: "relationship",
@ -122,15 +120,11 @@ export const Posts = buildVersionedCollectionConfig({
}, },
admin: { admin: {
condition: (_, siblingData) => { condition: (_, siblingData) => {
if ( if (isUndefined(siblingData.language) || isUndefined(siblingData.sourceLanguage)) {
isUndefined(siblingData.language) ||
isUndefined(siblingData.sourceLanguage)
) {
return false; return false;
} }
return siblingData.language !== siblingData.sourceLanguage; return siblingData.language !== siblingData.sourceLanguage;
}, },
width: "0%",
}, },
validate: (translators, { siblingData }) => { validate: (translators, { siblingData }) => {
if (isUndefined(siblingData.language) || isUndefined(siblingData.sourceLanguage)) { if (isUndefined(siblingData.language) || isUndefined(siblingData.sourceLanguage)) {
@ -150,11 +144,8 @@ export const Posts = buildVersionedCollectionConfig({
type: "relationship", type: "relationship",
relationTo: Collections.Recorders, relationTo: Collections.Recorders,
hasMany: true, hasMany: true,
admin: { width: "0%" },
}, },
], ]),
},
{ name: fields.content, type: "richText" },
], ],
}), }),
{ {

View File

@ -4,6 +4,7 @@ import { mustBeAdmin as mustBeAdminForFields } from "../../accesses/fields/mustB
import { QuickFilters } from "../../components/QuickFilters"; import { QuickFilters } from "../../components/QuickFilters";
import { CollectionGroups, Collections, RecordersRoles } from "../../constants"; import { CollectionGroups, Collections, RecordersRoles } from "../../constants";
import { imageField } from "../../fields/imageField/imageField"; import { imageField } from "../../fields/imageField/imageField";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor"; import { createEditor } from "../../utils/editor";
@ -77,23 +78,19 @@ export const Recorders = buildCollectionConfig({
endpoints: [importFromStrapi], endpoints: [importFromStrapi],
timestamps: false, timestamps: false,
fields: [ fields: [
{ rowField([
type: "row",
fields: [
{ {
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", width: "0%" }, admin: { description: "The username must be unique" },
}, },
imageField({ imageField({
name: fields.avatar, name: fields.avatar,
relationTo: Collections.RecordersThumbnails, relationTo: Collections.RecordersThumbnails,
admin: { width: "0%" },
}), }),
], ]),
},
{ {
name: fields.languages, name: fields.languages,
type: "relationship", type: "relationship",

View File

@ -1,6 +1,7 @@
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from "payload/types";
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { CollectionGroups, Collections, VideoSources } from "../../constants"; import { CollectionGroups, Collections, VideoSources } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
import { importFromStrapi } from "./endpoints/importFromStrapi"; import { importFromStrapi } from "./endpoints/importFromStrapi";
@ -47,10 +48,8 @@ export const Videos: CollectionConfig = buildCollectionConfig({
endpoints: [importFromStrapi], endpoints: [importFromStrapi],
timestamps: false, timestamps: false,
fields: [ fields: [
{ rowField([
type: "row", { name: fields.uid, type: "text", required: true, unique: true },
fields: [
{ name: fields.uid, type: "text", required: true, unique: true, admin: { width: "0%" } },
{ {
name: fields.gone, name: fields.gone,
type: "checkbox", type: "checkbox",
@ -59,7 +58,6 @@ export const Videos: CollectionConfig = buildCollectionConfig({
admin: { admin: {
description: description:
"Is the video no longer available (deleted, privatized, unlisted, blocked...)", "Is the video no longer available (deleted, privatized, unlisted, blocked...)",
width: "0%",
}, },
}, },
{ {
@ -67,20 +65,14 @@ export const Videos: CollectionConfig = buildCollectionConfig({
type: "select", type: "select",
required: true, required: true,
options: Object.entries(VideoSources).map(([value, label]) => ({ label, value })), options: Object.entries(VideoSources).map(([value, label]) => ({ label, value })),
admin: { width: "0%" },
}, },
], ]),
},
{ name: fields.title, type: "text", required: true }, { name: fields.title, type: "text", required: true },
{ name: fields.description, type: "textarea" }, { name: fields.description, type: "textarea" },
{ rowField([
type: "row", { name: fields.likes, type: "number" },
fields: [ { name: fields.views, type: "number" },
{ name: fields.likes, type: "number", admin: { width: "0%" } }, ]),
{ name: fields.views, type: "number", admin: { width: "0%" } },
],
},
{ {
name: fields.publishedDate, name: fields.publishedDate,
type: "date", type: "date",

View File

@ -1,6 +1,7 @@
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from "payload/types";
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { CollectionGroups, Collections } from "../../constants"; import { CollectionGroups, Collections } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = { const fields = {
@ -30,12 +31,9 @@ export const VideosChannels: CollectionConfig = buildCollectionConfig({
timestamps: false, timestamps: false,
fields: [ fields: [
{ name: fields.uid, type: "text", required: true, unique: true }, { name: fields.uid, type: "text", required: true, unique: true },
{ rowField([
type: "row",
fields: [
{ name: fields.title, type: "text", required: true }, { name: fields.title, type: "text", required: true },
{ name: fields.subscribers, type: "number" }, { name: fields.subscribers, type: "number" },
], ]),
},
], ],
}); });

View File

@ -2,6 +2,7 @@ import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types
import { CollectionGroups, Collections, KeysTypes } from "../../constants"; import { CollectionGroups, Collections, KeysTypes } from "../../constants";
import { imageField } from "../../fields/imageField/imageField"; import { imageField } from "../../fields/imageField/imageField";
import { keysField } from "../../fields/keysField/keysField"; import { keysField } from "../../fields/keysField/keysField";
import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField"; import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { createEditor } from "../../utils/editor"; import { createEditor } from "../../utils/editor";
@ -45,34 +46,25 @@ export const Weapons = buildVersionedCollectionConfig({
}, },
endpoints: [importFromStrapi, getBySlugEndpoint], endpoints: [importFromStrapi, getBySlugEndpoint],
fields: [ fields: [
{ rowField([
type: "row", slugField({ name: fields.slug }),
fields: [
slugField({ name: fields.slug, admin: { width: "0%" } }),
imageField({ imageField({
name: fields.thumbnail, name: fields.thumbnail,
relationTo: Collections.WeaponsThumbnails, relationTo: Collections.WeaponsThumbnails,
admin: { width: "0%" },
}), }),
], ]),
}, rowField([
{
type: "row",
fields: [
keysField({ keysField({
name: fields.type, name: fields.type,
relationTo: KeysTypes.Weapons, relationTo: KeysTypes.Weapons,
required: true, required: true,
admin: { allowCreate: false, width: "0%" },
}), }),
{ {
name: fields.group, name: fields.group,
type: "relationship", type: "relationship",
relationTo: Collections.WeaponsGroups, relationTo: Collections.WeaponsGroups,
admin: { width: "0%" },
},
],
}, },
]),
{ {
name: fields.appearances, name: fields.appearances,
type: "array", type: "array",
@ -91,7 +83,6 @@ export const Weapons = buildVersionedCollectionConfig({
required: true, required: true,
hasMany: true, hasMany: true,
relationTo: KeysTypes.Categories, relationTo: KeysTypes.Categories,
admin: { allowCreate: false },
}), }),
translatedFields({ translatedFields({
name: fields.appearancesTranslations, name: fields.appearancesTranslations,
@ -103,61 +94,46 @@ export const Weapons = buildVersionedCollectionConfig({
hasCredits: true, hasCredits: true,
}, },
fields: [ fields: [
{ rowField([
type: "row",
fields: [
{ {
name: fields.appearancesTranslationsName, name: fields.appearancesTranslationsName,
type: "text", type: "text",
required: true, required: true,
admin: { width: "0%" },
}, },
{ {
name: fields.appearancesTranslationsDescription, name: fields.appearancesTranslationsDescription,
type: "richText", type: "richText",
editor: createEditor({ inlines: true }), editor: createEditor({ inlines: true }),
admin: { width: "0%" },
}, },
], ]),
}, rowField([
{
type: "row",
fields: [
{ {
name: fields.appearancesTranslationsLevel1, name: fields.appearancesTranslationsLevel1,
label: "Level 1", label: "Level 1",
type: "richText", type: "richText",
editor: createEditor({ inlines: true }), editor: createEditor({ inlines: true }),
admin: { width: "0%" },
}, },
{ {
name: fields.appearancesTranslationsLevel2, name: fields.appearancesTranslationsLevel2,
label: "Level 2", label: "Level 2",
type: "richText", type: "richText",
editor: createEditor({ inlines: true }), editor: createEditor({ inlines: true }),
admin: { width: "0%" },
}, },
], ]),
}, rowField([
{
type: "row",
fields: [
{ {
name: fields.appearancesTranslationsLevel3, name: fields.appearancesTranslationsLevel3,
label: "Level 3", label: "Level 3",
type: "richText", type: "richText",
editor: createEditor({ inlines: true }), editor: createEditor({ inlines: true }),
admin: { width: "0%" },
}, },
{ {
name: fields.appearancesTranslationsLevel4, name: fields.appearancesTranslationsLevel4,
label: "Level 4", label: "Level 4",
type: "richText", type: "richText",
editor: createEditor({ inlines: true }), editor: createEditor({ inlines: true }),
admin: { width: "0%" },
},
],
}, },
]),
], ],
}), }),
], ],

View File

@ -38,6 +38,7 @@ type Params<S> = {
params: any; params: any;
}; };
payload: { payload: {
path?: string;
collection: Collections; collection: Collections;
import?: (strapiObject: S, user: any) => Promise<void>; import?: (strapiObject: S, user: any) => Promise<void>;
convert?: ( convert?: (
@ -55,9 +56,11 @@ export const importStrapiEntries = async <S>({
const entries = await getAllStrapiEntries(strapiParams.collection, strapiParams.params); const entries = await getAllStrapiEntries(strapiParams.collection, strapiParams.params);
const errors: string[] = []; const errors: string[] = [];
let currentCount = 1;
await Promise.all( for (const { attributes, id } of entries) {
entries.map(async ({ attributes, id }) => { console.debug(`Handling entry ${currentCount}/${entries.length} (id: ${id})`);
currentCount++;
try { try {
if (isDefined(payloadParams.import)) { if (isDefined(payloadParams.import)) {
await payloadParams.import(attributes, user); await payloadParams.import(attributes, user);
@ -73,18 +76,18 @@ export const importStrapiEntries = async <S>({
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
if (typeof e === "object" && isDefined(e) && "name" in e) { if (typeof e === "object" && isDefined(e) && "name" in e) {
errors.push(`${e.name} with ${id}`); const message = "message" in e ? ` (${e.message})` : "";
errors.push(`${e.name}${message} with ${id}`);
}
} }
} }
})
);
return { count: entries.length, errors }; return { count: entries.length, errors };
}; };
export const createStrapiImportEndpoint = <S>(params: Params<S>): CollectionEndpoint => ({ export const createStrapiImportEndpoint = <S>(params: Params<S>): CollectionEndpoint => ({
method: "post", method: "post",
path: "/strapi", path: params.payload.path ?? "/strapi",
handler: async (req, res) => { handler: async (req, res) => {
if (!req.user) { if (!req.user) {
return res.status(403).send({ return res.status(403).send({

View File

@ -2,7 +2,7 @@ import payload from "payload";
import { FieldBase } from "payload/dist/fields/config/types"; import { FieldBase } from "payload/dist/fields/config/types";
import { RelationshipField, Where } from "payload/types"; import { RelationshipField, Where } from "payload/types";
import { Collections } from "../../constants"; import { Collections } from "../../constants";
import { isNotEmpty } from "../../utils/asserts"; import { isEmpty } from "../../utils/asserts";
type BackPropagationField = FieldBase & { type BackPropagationField = FieldBase & {
where: (data: any) => Where; where: (data: any) => Where;
@ -30,21 +30,22 @@ export const backPropagationField = ({
], ],
afterRead: [ afterRead: [
...afterRead, ...afterRead,
async ({ data }) => { async ({ data, context }) => {
if (isNotEmpty(data?.id)) { if (isEmpty(data?.id) || context.stopPropagation) {
return hasMany ? [] : undefined;
}
const result = await payload.find({ const result = await payload.find({
collection: params.relationTo, collection: params.relationTo,
where: where(data), where: where(data),
limit: 100, limit: 100,
depth: 0, depth: 0,
context: { stopPropagation: true },
}); });
if (hasMany) { if (hasMany) {
return result.docs.map((doc) => doc.id); return result.docs.map((doc) => doc.id);
} else { } else {
return result.docs[0]?.id; return result.docs[0]?.id;
} }
}
return hasMany ? [] : undefined;
}, },
], ],
}, },

View File

@ -0,0 +1,55 @@
import { CollapsibleField, Condition, Field } from "payload/types";
import { capitalize } from "../../utils/string";
type Props = {
name: string;
label?: string;
admin?: {
description?: string;
condition?: Condition;
};
fields: Field[];
};
export const componentField = ({
name,
fields,
label = capitalize(name),
admin,
}: Props): CollapsibleField => {
return {
type: "collapsible",
label: name,
admin: { className: "component-field", condition: admin?.condition },
fields: [
{
name: `${name}Enabled`,
label,
type: "checkbox",
admin: {
className: "component-field-checkbox",
description: admin?.description,
},
},
{
name,
type: "group",
hooks: {
beforeChange: [
({ siblingData }) => {
if (!siblingData[`${name}Enabled`]) {
delete siblingData[name];
}
},
],
},
admin: {
className: "component-field-group",
condition: (_, siblingData) => siblingData[`${name}Enabled`],
hideGutter: true,
},
fields,
},
],
};
};

View File

@ -10,9 +10,14 @@ type KeysField = FieldBase & {
export const keysField = ({ export const keysField = ({
relationTo, relationTo,
hasMany = false, hasMany = false,
admin,
...props ...props
}: KeysField): RelationshipField => ({ }: KeysField): RelationshipField => ({
...props, ...props,
admin: {
allowCreate: false,
...admin,
},
type: "relationship", type: "relationship",
hasMany: hasMany, hasMany: hasMany,
relationTo: Collections.Keys, relationTo: Collections.Keys,

View File

@ -1,14 +0,0 @@
import { ArrayField } from "payload/types";
type Props = Omit<ArrayField, "type" | "maxRows" | "minRows">;
export const optionalGroupField = ({
admin: { className = "", ...otherAdmin } = {},
...otherProps
}: Props): ArrayField => ({
...otherProps,
type: "array",
minRows: 0,
maxRows: 1,
admin: { ...otherAdmin, className: `${className} group-array` },
});

View File

@ -0,0 +1,9 @@
import { Field, RowField } from "payload/types";
export const rowField = (fields: Field[]): RowField => ({
type: "row",
fields: fields.map(({ admin, ...otherConfig }) => ({
...otherConfig,
admin: { width: "0%", ...admin },
})),
});

View File

@ -3,6 +3,7 @@ import { array } from "payload/dist/fields/validations";
import { ArrayField, Field } from "payload/types"; import { ArrayField, Field } from "payload/types";
import { Collections } from "../../constants"; import { Collections } from "../../constants";
import { hasDuplicates, isDefined, isUndefined } from "../../utils/asserts"; import { hasDuplicates, isDefined, isUndefined } from "../../utils/asserts";
import { rowField } from "../rowField/rowField";
import { Cell } from "./Cell"; import { Cell } from "./Cell";
import { RowLabel } from "./RowLabel"; import { RowLabel } from "./RowLabel";
@ -169,9 +170,7 @@ export const translatedFields = ({
return true; return true;
}, },
fields: [ fields: [
hasSourceLanguage rowField(hasSourceLanguage ? [languageField, sourceLanguageField] : [languageField]),
? { type: "row", fields: [languageField, sourceLanguageField] }
: languageField,
...fields, ...fields,
...(hasCredits ? [creditFields] : []), ...(hasCredits ? [creditFields] : []),
], ],

View File

@ -1,3 +1,5 @@
// Theme override
html[data-theme="dark"] { html[data-theme="dark"] {
--color-base-0: #ffffff; --color-base-0: #ffffff;
--color-base-50: #ebeae7; --color-base-50: #ebeae7;
@ -46,8 +48,14 @@ html[data-theme="light"] {
--color-base-1000: #000000; --color-base-1000: #000000;
} }
// Fix height disparities between certain fields
.field-type.row { .field-type.row {
padding: 1rem 0; padding: 1rem 0;
> div > .field-type.checkbox {
padding-top: 2.4rem;
}
} }
.field-type.radio-group { .field-type.radio-group {
@ -56,6 +64,8 @@ html[data-theme="light"] {
} }
} }
// Hide header on blocks with classname "no-label"
.field-type.no-label > header { .field-type.no-label > header {
display: none; display: none;
} }
@ -68,37 +78,49 @@ html[data-theme="light"] {
display: none; display: none;
} }
// Reduce margin on Lexical blocks with the classname "reduced-margins"
.rich-text-lexical.field-type.reduced-margins { .rich-text-lexical.field-type.reduced-margins {
margin-top: -0.75em; margin-top: -0.75em;
margin-bottom: -2rem; margin-bottom: -2rem;
} }
.field-type.array-field.group-array { // CSS for componentField
> .array-field__header {
.array-field__header-actions { .collapsible-field.component-field {
display: none; > .collapsible > .collapsible__toggle-wrap {
}
}
> div {
> div {
> div {
> .collapsible__toggle-wrap {
.array-actions__duplicate {
display: none; display: none;
} }
.collapsible__drag, .component-field-checkbox {
.collapsible__toggle, margin-bottom: 0;
.collapsible__header-wrap,
.collapsible__indicator { .checkbox-input {
display: none; flex-direction: row-reverse;
justify-content: space-between;
display: flex;
gap: 16px;
pointer-events: none;
> .checkbox-input__input {
pointer-events: auto;
}
} }
.collapsible__actions-wrap { .field-label {
z-index: 1; font-weight: 500;
} font-family: "Suisse Intl";
} font-size: 1.7788461538rem;
} padding: 0;
transform: translateY(2px);
}
}
.component-field-group {
margin-top: 2rem;
> .group-field__wrap > .group-field__header {
display: none;
} }
} }
} }

View File

@ -22,32 +22,32 @@ export type RecorderBiographies = {
export interface Config { export interface Config {
collections: { collections: {
'library-folders': LibraryFolder; "library-folders": LibraryFolder;
'library-items': LibraryItem; "library-items": LibraryItem;
contents: Content; contents: Content;
'contents-folders': ContentsFolder; "contents-folders": ContentsFolder;
posts: Post; posts: Post;
'chronology-items': ChronologyItem; "chronology-items": ChronologyItem;
'chronology-eras': ChronologyEra; "chronology-eras": ChronologyEra;
weapons: Weapon; weapons: Weapon;
'weapons-groups': WeaponsGroup; "weapons-groups": WeaponsGroup;
'weapons-thumbnails': WeaponsThumbnail; "weapons-thumbnails": WeaponsThumbnail;
'contents-thumbnails': ContentsThumbnail; "contents-thumbnails": ContentsThumbnail;
'library-items-thumbnails': LibraryItemThumbnail; "library-items-thumbnails": LibraryItemThumbnail;
'library-items-scans': LibraryItemScans; "library-items-scans": LibraryItemScans;
'library-items-gallery': LibraryItemGallery; "library-items-gallery": LibraryItemGallery;
'recorders-thumbnails': RecordersThumbnail; "recorders-thumbnails": RecordersThumbnail;
'posts-thumbnails': PostThumbnail; "posts-thumbnails": PostThumbnail;
files: File; files: File;
Notes: Note; Notes: Note;
videos: Video; videos: Video;
'videos-channels': VideosChannel; "videos-channels": VideosChannel;
languages: Language; languages: Language;
currencies: Currency; currencies: Currency;
recorders: Recorder; recorders: Recorder;
keys: Key; keys: Key;
'payload-preferences': PayloadPreference; "payload-preferences": PayloadPreference;
'payload-migrations': PayloadMigration; "payload-migrations": PayloadMigration;
}; };
globals: {}; globals: {};
} }
@ -62,6 +62,7 @@ export interface LibraryFolder {
}[]; }[];
id?: string; id?: string;
}[]; }[];
parentFolders?: string[] | LibraryFolder[];
subfolders?: string[] | LibraryFolder[]; subfolders?: string[] | LibraryFolder[];
items?: string[] | LibraryItem[]; items?: string[] | LibraryItem[];
} }
@ -71,20 +72,20 @@ export interface Language {
} }
export interface LibraryItem { export interface LibraryItem {
id: string; id: string;
itemType?: 'Textual' | 'Audio' | 'Video' | 'Game' | 'Other'; itemType?: "Textual" | "Audio" | "Video" | "Game" | "Other";
digital: boolean;
slug: string; slug: string;
thumbnail?: string | LibraryItemThumbnail; thumbnail?: string | LibraryItemThumbnail;
pretitle?: string; pretitle?: string;
title: string; title: string;
subtitle?: string; subtitle?: string;
rootItem: boolean; digital: boolean;
primary: boolean;
gallery?: { gallery?: {
image?: string | LibraryItemGallery; image?: string | LibraryItemGallery;
id?: string; id?: string;
}[]; }[];
scansEnabled?: boolean;
scans?: { scans?: {
coverEnabled?: boolean;
cover?: { cover?: {
front?: string | LibraryItemScans; front?: string | LibraryItemScans;
spine?: string | LibraryItemScans; spine?: string | LibraryItemScans;
@ -94,8 +95,8 @@ export interface LibraryItem {
flapBack?: string | LibraryItemScans; flapBack?: string | LibraryItemScans;
insideFlapFront?: string | LibraryItemScans; insideFlapFront?: string | LibraryItemScans;
insideFlapBack?: string | LibraryItemScans; insideFlapBack?: string | LibraryItemScans;
id?: string; };
}[]; dustjacketEnabled?: boolean;
dustjacket?: { dustjacket?: {
front?: string | LibraryItemScans; front?: string | LibraryItemScans;
spine?: string | LibraryItemScans; spine?: string | LibraryItemScans;
@ -107,8 +108,8 @@ export interface LibraryItem {
flapBack?: string | LibraryItemScans; flapBack?: string | LibraryItemScans;
insideFlapFront?: string | LibraryItemScans; insideFlapFront?: string | LibraryItemScans;
insideFlapBack?: string | LibraryItemScans; insideFlapBack?: string | LibraryItemScans;
id?: string; };
}[]; obiEnabled?: boolean;
obi?: { obi?: {
front?: string | LibraryItemScans; front?: string | LibraryItemScans;
spine?: string | LibraryItemScans; spine?: string | LibraryItemScans;
@ -120,22 +121,19 @@ export interface LibraryItem {
flapBack?: string | LibraryItemScans; flapBack?: string | LibraryItemScans;
insideFlapFront?: string | LibraryItemScans; insideFlapFront?: string | LibraryItemScans;
insideFlapBack?: string | LibraryItemScans; insideFlapBack?: string | LibraryItemScans;
id?: string; };
}[];
pages?: { pages?: {
page: number; page: number;
image: string | LibraryItemScans; image: string | LibraryItemScans;
id?: string; id?: string;
}[]; }[];
downloadable: boolean; };
id?: string;
}[];
textual?: { textual?: {
subtype?: string[] | Key[]; subtype?: string[] | Key[];
languages?: string[] | Language[]; languages?: string[] | Language[];
pageCount?: number; pageCount?: number;
bindingType?: 'Paperback' | 'Hardcover'; bindingType?: "Paperback" | "Hardcover";
pageOrder?: 'LeftToRight' | 'RightToLeft'; pageOrder?: "LeftToRight" | "RightToLeft";
}; };
audio?: { audio?: {
audioSubtype?: string[] | Key[]; audioSubtype?: string[] | Key[];
@ -147,6 +145,17 @@ export interface LibraryItem {
}; };
releaseDate?: string; releaseDate?: string;
categories?: string[] | Key[]; categories?: string[] | Key[];
sizeEnabled?: boolean;
size?: {
width: number;
height: number;
thickness?: number;
};
priceEnabled?: boolean;
price?: {
amount: number;
currency: string | Currency;
};
translations?: { translations?: {
language: string | Language; language: string | Language;
description: { description: {
@ -154,21 +163,13 @@ export interface LibraryItem {
}[]; }[];
id?: string; id?: string;
}[]; }[];
size?: {
width: number;
height: number;
thickness?: number;
id?: string;
}[];
price?: {
amount: number;
currency: string | Currency;
id?: string;
}[];
urls?: { urls?: {
url: string; url: string;
id?: string; id?: string;
}[]; }[];
parentFolders?: string[] | LibraryFolder[];
parentItems?: string[] | LibraryItem[];
subitems?: string[] | LibraryItem[];
contents?: { contents?: {
content: string | Content; content: string | Content;
pageStart?: number; pageStart?: number;
@ -183,7 +184,7 @@ export interface LibraryItem {
updatedBy: string | Recorder; updatedBy: string | Recorder;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: 'draft' | 'published'; _status?: "draft" | "published";
} }
export interface LibraryItemThumbnail { export interface LibraryItemThumbnail {
id: string; id: string;
@ -301,22 +302,22 @@ export interface Key {
id: string; id: string;
name: string; name: string;
type: type:
| 'Contents' | "Contents"
| 'LibraryAudio' | "LibraryAudio"
| 'LibraryVideo' | "LibraryVideo"
| 'LibraryTextual' | "LibraryTextual"
| 'LibraryGroup' | "LibraryGroup"
| 'Library' | "Library"
| 'Weapons' | "Weapons"
| 'GamePlatforms' | "GamePlatforms"
| 'Categories' | "Categories"
| 'Wordings'; | "Wordings";
translations?: CategoryTranslations; translations?: CategoryTranslations;
} }
export interface File { export interface File {
id: string; id: string;
filename: string; filename: string;
type: 'LibraryScans' | 'LibrarySoundtracks' | 'ContentVideo' | 'ContentAudio'; type: "LibraryScans" | "LibrarySoundtracks" | "ContentVideo" | "ContentAudio";
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@ -329,6 +330,7 @@ export interface Content {
thumbnail?: string | ContentsThumbnail; thumbnail?: string | ContentsThumbnail;
categories?: string[] | Key[]; categories?: string[] | Key[];
type?: string | Key; type?: string | Key;
libraryItems?: string[] | LibraryItem[];
translations: { translations: {
language: string | Language; language: string | Language;
sourceLanguage: string | Language; sourceLanguage: string | Language;
@ -354,10 +356,13 @@ export interface Content {
audio?: string | File; audio?: string | File;
id?: string; id?: string;
}[]; }[];
folders?: string[] | ContentsFolder[];
previousContents?: string[] | Content[];
nextContents?: string[] | Content[];
updatedBy: string | Recorder; updatedBy: string | Recorder;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: 'draft' | 'published'; _status?: "draft" | "published";
} }
export interface ContentsThumbnail { export interface ContentsThumbnail {
id: string; id: string;
@ -403,7 +408,7 @@ export interface Recorder {
avatar?: string | RecordersThumbnail; avatar?: string | RecordersThumbnail;
languages?: string[] | Language[]; languages?: string[] | Language[];
biographies?: RecorderBiographies; biographies?: RecorderBiographies;
role?: ('Admin' | 'Recorder' | 'Api')[]; role?: ("Admin" | "Recorder" | "Api")[];
anonymize: boolean; anonymize: boolean;
email: string; email: string;
resetPasswordToken?: string; resetPasswordToken?: string;
@ -461,20 +466,20 @@ export interface Post {
thumbnail?: string | PostThumbnail; thumbnail?: string | PostThumbnail;
authors: authors:
| { | {
relationTo: 'recorders'; relationTo: "recorders";
value: string; value: string;
}[] }[]
| { | {
relationTo: 'recorders'; relationTo: "recorders";
value: Recorder; value: Recorder;
}[]; }[];
categories?: categories?:
| { | {
relationTo: 'keys'; relationTo: "keys";
value: string; value: string;
}[] }[]
| { | {
relationTo: 'keys'; relationTo: "keys";
value: Key; value: Key;
}[]; }[];
translations: { translations: {
@ -484,11 +489,11 @@ export interface Post {
summary?: { summary?: {
[k: string]: unknown; [k: string]: unknown;
}[]; }[];
translators?: string[] | Recorder[];
proofreaders?: string[] | Recorder[];
content?: { content?: {
[k: string]: unknown; [k: string]: unknown;
}[]; }[];
translators?: string[] | Recorder[];
proofreaders?: string[] | Recorder[];
id?: string; id?: string;
}[]; }[];
publishedDate: string; publishedDate: string;
@ -496,7 +501,7 @@ export interface Post {
updatedBy: string | Recorder; updatedBy: string | Recorder;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: 'draft' | 'published'; _status?: "draft" | "published";
} }
export interface PostThumbnail { export interface PostThumbnail {
id: string; id: string;
@ -547,11 +552,11 @@ export interface ChronologyItem {
events: { events: {
source?: source?:
| { | {
relationTo: 'contents'; relationTo: "contents";
value: string | Content; value: string | Content;
} }
| { | {
relationTo: 'library-items'; relationTo: "library-items";
value: string | LibraryItem; value: string | LibraryItem;
}; };
translations: { translations: {
@ -574,7 +579,7 @@ export interface ChronologyItem {
updatedBy: string | Recorder; updatedBy: string | Recorder;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: 'draft' | 'published'; _status?: "draft" | "published";
} }
export interface ChronologyEra { export interface ChronologyEra {
id: string; id: string;
@ -630,7 +635,7 @@ export interface Weapon {
updatedBy: string | Recorder; updatedBy: string | Recorder;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: 'draft' | 'published'; _status?: "draft" | "published";
} }
export interface WeaponsThumbnail { export interface WeaponsThumbnail {
id: string; id: string;
@ -700,7 +705,7 @@ export interface Video {
id: string; id: string;
uid: string; uid: string;
gone: boolean; gone: boolean;
source: 'YouTube' | 'NicoNico' | 'Tumblr'; source: "YouTube" | "NicoNico" | "Tumblr";
title: string; title: string;
description?: string; description?: string;
likes?: number; likes?: number;
@ -717,7 +722,7 @@ export interface VideosChannel {
export interface PayloadPreference { export interface PayloadPreference {
id: string; id: string;
user: { user: {
relationTo: 'recorders'; relationTo: "recorders";
value: string | Recorder; value: string | Recorder;
}; };
key?: string; key?: string;
@ -741,7 +746,6 @@ export interface PayloadMigration {
createdAt: string; createdAt: string;
} }
declare module "payload" {
declare module 'payload' {
export interface GeneratedTypes extends Config {} export interface GeneratedTypes extends Config {}
} }

View File

@ -7,6 +7,7 @@ export const findWeaponType = async (name: string): Promise<string> => {
const key = await payload.find({ const key = await payload.find({
collection: Collections.Keys, collection: Collections.Keys,
where: { name: { equals: name }, type: { equals: KeysTypes.Weapons } }, where: { name: { equals: name }, type: { equals: KeysTypes.Weapons } },
depth: 0,
}); });
if (!key.docs[0]) throw new Error(`Weapon type ${name} wasn't found`); if (!key.docs[0]) throw new Error(`Weapon type ${name} wasn't found`);
return key.docs[0].id; return key.docs[0].id;
@ -16,27 +17,40 @@ export const findCategory = async (name: string): Promise<string> => {
const key = await payload.find({ const key = await payload.find({
collection: Collections.Keys, collection: Collections.Keys,
where: { name: { equals: name }, type: { equals: KeysTypes.Categories } }, where: { name: { equals: name }, type: { equals: KeysTypes.Categories } },
depth: 0,
}); });
if (!key.docs[0]) throw new Error(`Category ${name} wasn't found`); if (!key.docs[0]) throw new Error(`Category ${name} wasn't found`);
return key.docs[0]?.id; return key.docs[0].id;
}; };
export const findRecorder = async (name: string): Promise<string> => { export const findRecorder = async (name: string): Promise<string> => {
const recorder = await payload.find({ const recorder = await payload.find({
collection: Collections.Recorders, collection: Collections.Recorders,
where: { username: { equals: name } }, where: { username: { equals: name } },
depth: 0,
}); });
if (!recorder.docs[0]) throw new Error(`Recorder ${name} wasn't found`); if (!recorder.docs[0]) throw new Error(`Recorder ${name} wasn't found`);
return recorder.docs[0]?.id; return recorder.docs[0].id;
}; };
export const findContentType = async (name: string): Promise<string> => { export const findContentType = async (name: string): Promise<string> => {
const key = await payload.find({ const key = await payload.find({
collection: Collections.Keys, collection: Collections.Keys,
where: { name: { equals: name }, type: { equals: KeysTypes.Contents } }, where: { name: { equals: name }, type: { equals: KeysTypes.Contents } },
depth: 0,
}); });
if (!key.docs[0]) throw new Error(`Content type ${name} wasn't found`); if (!key.docs[0]) throw new Error(`Content type ${name} wasn't found`);
return key.docs[0]?.id; return key.docs[0].id;
};
export const findContent = async (slug: string): Promise<string> => {
const content = await payload.find({
collection: Collections.Contents,
where: { slug: { equals: slug } },
depth: 0,
});
if (!content.docs[0]) throw new Error(`Content ${slug} wasn't found`);
return content.docs[0].id;
}; };
type UploadStrapiImage = { type UploadStrapiImage = {

View File

@ -7,7 +7,7 @@ export const shortenEllipsis = (text: string, length: number): string =>
export const formatLanguageCode = (code: string): string => export const formatLanguageCode = (code: string): string =>
tags(code).valid() ? tags(code).language()?.descriptions()[0] ?? code : code; tags(code).valid() ? tags(code).language()?.descriptions()[0] ?? code : code;
const capitalize = (string: string): string => { export const capitalize = (string: string): string => {
const [firstLetter, ...otherLetters] = string; const [firstLetter, ...otherLetters] = string;
if (isUndefined(firstLetter)) return ""; if (isUndefined(firstLetter)) return "";
return [firstLetter.toUpperCase(), ...otherLetters].join(""); return [firstLetter.toUpperCase(), ...otherLetters].join("");

View File

@ -14,7 +14,7 @@ const updatedByField = (): RelationshipField => ({
type: "relationship", type: "relationship",
required: true, required: true,
relationTo: Collections.Recorders, relationTo: Collections.Recorders,
admin: { readOnly: true }, admin: { readOnly: true, hidden: true },
}); });
type BuildVersionedCollectionConfig = Omit<BuildCollectionConfig, "timestamps" | "versions">; type BuildVersionedCollectionConfig = Omit<BuildCollectionConfig, "timestamps" | "versions">;