Added chronology events, removed eras

This commit is contained in:
DrMint 2024-03-20 13:31:24 +01:00
parent e91642616d
commit 4fd59464ba
24 changed files with 420 additions and 378 deletions

@ -1,87 +0,0 @@
import { CollectionConfig } from "payload/types";
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { CollectionGroups, Collections } from "../../constants";
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor";
import { getAllEndpoint } from "./endpoints/getAllEndpoint";
import { importFromStrapi } from "./endpoints/importFromStrapi";
import { beforeValidateEndingGreaterThanStarting } from "./hooks/beforeValidateEndingGreaterThanStarting";
import { beforeValidateNoIntersection } from "./hooks/beforeValidateNoIntersection";
const fields = {
slug: "slug",
startingYear: "startingYear",
endingYear: "endingYear",
translations: "translations",
translationsTitle: "title",
translationsDescription: "description",
events: "events",
} as const satisfies Record<string, string>;
export const ChronologyEras: CollectionConfig = buildCollectionConfig({
slug: Collections.ChronologyEras,
labels: {
singular: "Chronology Era",
plural: "Chronology Eras",
},
defaultSort: fields.startingYear,
admin: {
group: CollectionGroups.Collections,
defaultColumns: [fields.slug, fields.startingYear, fields.endingYear, fields.translations],
useAsTitle: fields.slug,
},
access: {
create: mustBeAdmin,
delete: mustBeAdmin,
},
hooks: {
beforeValidate: [beforeValidateEndingGreaterThanStarting, beforeValidateNoIntersection],
},
endpoints: [importFromStrapi, getAllEndpoint],
fields: [
slugField({ name: fields.slug }),
rowField([
{
name: fields.startingYear,
type: "number",
min: 0,
required: true,
admin: { description: "The year the era started (year included)" },
},
{
name: fields.endingYear,
type: "number",
min: 0,
required: true,
admin: { description: "The year the era ended (year included)" },
},
]),
translatedFields({
name: fields.translations,
admin: { useAsTitle: fields.translationsTitle },
fields: [
{ name: fields.translationsTitle, type: "text", required: true },
{
name: fields.translationsDescription,
type: "richText",
editor: createEditor({ inlines: true, lists: true, links: true }),
},
],
}),
backPropagationField({
name: fields.events,
hasMany: true,
relationTo: Collections.ChronologyItems,
where: ({ startingYear, endingYear }) => ({
and: [
{ "date.year": { greater_than_equal: startingYear } },
{ "date.year": { less_than_equal: endingYear } },
],
}),
}),
],
});

@ -1,99 +0,0 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointEra } from "../../../sdk";
import { ChronologyEra, ChronologyItem } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isDefined, isPayloadArrayType, isPayloadType } from "../../../utils/asserts";
import { handleRecorder } from "../../../utils/endpoints";
export const getAllEndpoint: CollectionEndpoint = {
method: "get",
path: "/all",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
const eras: ChronologyEra[] = (
await payload.find({
collection: Collections.ChronologyEras,
pagination: false,
})
).docs;
const result = eras.map<EndpointEra>(
({ endingYear, startingYear, slug, translations, events: items }) => ({
slug,
startingYear,
endingYear,
translations:
translations?.map(({ language, title, description }) => ({
language: isPayloadType(language) ? language.id : language,
title,
...(description ? { description } : {}),
})) ?? [],
items:
items
?.filter(isPayloadType<ChronologyItem>)
.sort((a, b) => {
const aYear = a.date.year;
const bYear = b.date.year;
if (aYear !== bYear) return aYear - bYear;
const aMonth = a.date.month ?? 0;
const bMonth = b.date.month ?? 0;
if (aMonth !== bMonth) return aMonth - bMonth;
const aDay = a.date.day ?? 0;
const bDay = b.date.day ?? 0;
if (aDay !== bDay) return aDay - bDay;
return 0;
})
.map(({ events, date: { year, day, month } }) => ({
date: {
year,
...(isDefined(day) ? { day } : {}),
...(isDefined(month) ? { month } : {}),
},
events: events.map(({ translations }) => ({
translations: translations.map(
({
language,
sourceLanguage,
description,
notes,
proofreaders,
transcribers,
translators,
title,
}) => ({
language: isPayloadType(language) ? language.id : language,
sourceLanguage: isPayloadType(sourceLanguage)
? sourceLanguage.id
: sourceLanguage,
...(title ? { title } : {}),
...(description ? { description } : {}),
...(notes ? { notes } : {}),
proofreaders: isPayloadArrayType(proofreaders)
? proofreaders.map(handleRecorder)
: [],
transcribers: isPayloadArrayType(transcribers)
? transcribers.map(handleRecorder)
: [],
translators: isPayloadArrayType(translators)
? translators.map(handleRecorder)
: [],
})
),
})),
})) ?? [],
})
);
res.status(200).json(result);
},
};

@ -1,38 +0,0 @@
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { StrapiLanguage } from "../../../types/strapi";
import { isDefined, isUndefined } from "../../../utils/asserts";
import { plainTextToLexical } from "../../../utils/string";
type StrapiChronologyEra = {
slug: string;
starting_year: number;
ending_year: number;
title: { title: string; language: StrapiLanguage; description?: string }[];
};
export const importFromStrapi = createStrapiImportEndpoint<StrapiChronologyEra>({
strapi: {
collection: "chronology-eras",
params: {
populate: { title: { populate: "language" } },
},
},
payload: {
collection: Collections.ChronologyEras,
convert: ({ slug, starting_year, ending_year, title: titles }) => ({
slug,
startingYear: starting_year,
endingYear: ending_year,
translations: titles.map(({ language, title, description }) => {
if (isUndefined(language.data))
throw new Error("Language is undefined for one of the translations");
return {
language: language.data?.attributes.code,
title,
...(isDefined(description) ? { description: plainTextToLexical(description) } : {}),
};
}),
}),
},
});

@ -1,15 +0,0 @@
import { CollectionBeforeValidateHook } from "payload/types";
import { ChronologyEra } from "../../../types/collections";
import { isUndefined } from "../../../utils/asserts";
export const beforeValidateEndingGreaterThanStarting: CollectionBeforeValidateHook<
ChronologyEra
> = async ({ data }) => {
if (isUndefined(data)) throw new Error("The data is undefined");
const { startingYear, endingYear } = data;
if (isUndefined(endingYear)) throw new Error("Ending year is undefined");
if (isUndefined(startingYear)) throw new Error("Starting year is undefined");
if (endingYear < startingYear) {
throw new Error("The ending year cannot be before the starting year.");
}
};

@ -1,28 +0,0 @@
import payload from "payload";
import { CollectionBeforeValidateHook } from "payload/types";
import { Collections } from "../../../constants";
import { ChronologyEra } from "../../../types/collections";
import { hasIntersection, isUndefined } from "../../../utils/asserts";
export const beforeValidateNoIntersection: CollectionBeforeValidateHook<ChronologyEra> = async ({
data,
}) => {
if (isUndefined(data)) throw new Error("The data is undefined");
const { startingYear, endingYear } = data;
if (isUndefined(endingYear)) throw new Error("Ending year is undefined");
if (isUndefined(startingYear)) throw new Error("Starting year is undefined");
const otherEras = await payload.find({
collection: Collections.ChronologyEras,
limit: 100,
});
otherEras.docs.forEach((otherEra: ChronologyEra) => {
if (hasIntersection([startingYear, endingYear], [otherEra.startingYear, otherEra.endingYear])) {
throw new Error(
`This era (${startingYear} -> ${endingYear}) is intersecting with the era\
"${otherEra.slug}" (${otherEra.startingYear} -> ${otherEra.endingYear})`
);
}
});
};

@ -9,6 +9,11 @@ import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { createEditor } from "../../utils/editor";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
import { collectibleBlock } from "./blocks/collectibleBlock";
import { pageBlock } from "./blocks/contentBlock";
import { urlBlock } from "./blocks/urlBlock";
import { getAllEndpoint } from "./endpoints/getAllEndpoint";
import { getByID } from "./endpoints/getByID";
import { importFromStrapi } from "./endpoints/importFromStrapi";
import { beforeValidatePopulateNameField } from "./hooks/beforeValidatePopulateNameField";
import { validateDate } from "./validations/validateDate";
@ -18,6 +23,7 @@ import { validateEventsTranslationsTitle } from "./validations/validateEventsTra
const fields = {
name: "name",
events: "events",
eventsSources: "sources",
eventsTranslations: "translations",
eventsTranslationsTitle: "title",
eventsTranslationsDescription: "description",
@ -26,15 +32,14 @@ const fields = {
dateYear: "year",
dateMonth: "month",
dateDay: "day",
era: "era",
status: "_status",
} as const satisfies Record<string, string>;
export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig({
slug: Collections.ChronologyItems,
export const ChronologyEvents: CollectionConfig = buildVersionedCollectionConfig({
slug: Collections.ChronologyEvents,
labels: {
singular: "Chronology Item",
plural: "Chronology Items",
singular: "Chronology Event",
plural: "Chronology Events",
},
defaultSort: fields.name,
admin: {
@ -45,7 +50,7 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig(
BeforeListTable: [
() =>
QuickFilters({
slug: Collections.ChronologyItems,
slug: Collections.ChronologyEvents,
filterGroups: [
languageBasedFilters("events.translations.language"),
publishStatusFilters,
@ -54,7 +59,7 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig(
],
},
},
endpoints: [importFromStrapi],
endpoints: [importFromStrapi, getAllEndpoint, getByID],
fields: [
{
name: fields.name,
@ -67,6 +72,11 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig(
{
type: "group",
name: fields.date,
admin: {
description:
"Make sure there isn't already an entry in the Chronology Events with the same date.\
If you try to create another entry with the same date, it will refuse to publish.",
},
validate: validateDate,
fields: [
rowField([
@ -97,6 +107,12 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig(
required: true,
minRows: 1,
fields: [
{
name: fields.eventsSources,
type: "blocks",
maxRows: 1,
blocks: [urlBlock, collectibleBlock, pageBlock],
},
translatedFields({
name: fields.eventsTranslations,
required: true,

@ -0,0 +1,81 @@
import { Block } from "payload/types";
import { Collections } from "../../../constants";
import { translatedFields } from "../../../fields/translatedFields/translatedFields";
export const collectibleBlock: Block = {
slug: "collectibleBlock",
interfaceName: "CollectibleBlock",
labels: { singular: "Collectible", plural: "Collectibles" },
fields: [
{
name: "collectible",
type: "relationship",
hasMany: false,
required: true,
relationTo: Collections.Collectibles,
},
{
name: "range",
type: "blocks",
maxRows: 1,
admin: { className: "no-label" },
blocks: [
{
slug: "page",
labels: { singular: "Page", plural: "Pages" },
fields: [
{
name: "page",
type: "number",
required: true,
admin: {
description:
"Make sure the page range corresponds to the pages as written\
on the collectible. You can use negative page numbers if necessary.",
},
},
],
},
{
slug: "timestamp",
labels: { singular: "Timestamp", plural: "Timestamps" },
fields: [
{
name: "timestamp",
type: "text",
required: true,
defaultValue: "00:00:00",
validate: (value) =>
/\d{2}:\d{2}:\d{2}/g.test(value)
? true
: "The format should be hh:mm:ss\
(e.g: 01:23:45 for 1 hour, 23 minutes, and 45 seconds)",
admin: {
description: "hh:mm:ss (e.g: 01:23:45 for 1 hour, 23 minutes, and 45 seconds)",
},
},
],
},
{
slug: "other",
labels: { singular: "Other", plural: "Others" },
fields: [
translatedFields({
admin: { className: "no-label" },
name: "translations",
required: true,
minRows: 1,
fields: [
{
name: "note",
type: "textarea",
required: true,
},
],
}),
],
},
],
},
],
};

@ -0,0 +1,17 @@
import { Block } from "payload/types";
import { Collections } from "../../../constants";
export const pageBlock: Block = {
slug: "pageBlock",
interfaceName: "PageBlock",
labels: { singular: "Page", plural: "Pages" },
fields: [
{
name: "page",
type: "relationship",
hasMany: false,
required: true,
relationTo: Collections.Pages,
},
],
};

@ -0,0 +1,8 @@
import { Block } from "payload/types";
export const urlBlock: Block = {
slug: "urlBlock",
interfaceName: "UrlBlock",
labels: { singular: "URL", plural: "URLs" },
fields: [{ name: "url", type: "text", required: true }],
};

@ -0,0 +1,150 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointChronologyEvent, EndpointSource } from "../../../sdk";
import { ChronologyEvent, CollectibleBlock } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isDefined, isNotEmpty, isPayloadArrayType, isPayloadType } from "../../../utils/asserts";
import { getDomainFromUrl, handleRecorder } from "../../../utils/endpoints";
import { convertCollectibleToPreview } from "../../Collectibles/endpoints/getBySlugEndpoint";
import { convertPageToPreview } from "../../Pages/endpoints/getBySlugEndpoint";
export const getAllEndpoint: CollectionEndpoint = {
method: "get",
path: "/all",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
const result = (
await payload.find({
collection: Collections.ChronologyEvents,
pagination: false,
draft: false,
where: {
_status: {
equals: "published",
},
},
})
).docs;
const events = result
.sort((a, b) => {
const aYear = a.date.year;
const bYear = b.date.year;
if (aYear !== bYear) return aYear - bYear;
const aMonth = a.date.month ?? 13;
const bMonth = b.date.month ?? 13;
if (aMonth !== bMonth) return aMonth - bMonth;
const aDay = a.date.day ?? 32;
const bDay = b.date.day ?? 32;
if (aDay !== bDay) return aDay - bDay;
return 0;
})
.map<EndpointChronologyEvent>(eventToEndpointEvent);
res.status(200).json(events);
},
};
export const eventToEndpointEvent = ({
date: { year, day, month },
events,
id,
}: ChronologyEvent): EndpointChronologyEvent => ({
id,
date: {
year,
...(isDefined(month) ? { month } : {}),
...(isDefined(day) ? { day } : {}),
},
events: events.map<EndpointChronologyEvent["events"][number]>(({ sources, translations }) => ({
translations: translations.map(
({
language,
sourceLanguage,
description,
notes,
proofreaders,
title,
transcribers,
translators,
}) => ({
language: isPayloadType(language) ? language.id : language,
sourceLanguage: isPayloadType(sourceLanguage) ? sourceLanguage.id : sourceLanguage,
...(isNotEmpty(title) ? { title } : {}),
...(isDefined(description) ? { description } : {}),
...(isDefined(notes) ? { notes } : {}),
proofreaders: isPayloadArrayType(proofreaders) ? proofreaders.map(handleRecorder) : [],
transcribers: isPayloadArrayType(transcribers) ? transcribers.map(handleRecorder) : [],
translators: isPayloadArrayType(translators) ? translators.map(handleRecorder) : [],
})
),
sources: handleSources(sources),
})),
});
const handleSources = (sources: ChronologyEvent["events"][number]["sources"]): EndpointSource[] => {
return (
sources?.flatMap<EndpointSource>((source) => {
switch (source.blockType) {
case "collectibleBlock":
const range = handleRange(source.range);
if (!isPayloadType(source.collectible)) return [];
return {
type: "collectible",
collectible: convertCollectibleToPreview(source.collectible),
...(range ? { range } : {}),
};
case "pageBlock":
if (!isPayloadType(source.page)) return [];
return {
type: "page",
page: convertPageToPreview(source.page),
};
case "urlBlock":
return {
type: "url",
url: source.url,
label: getDomainFromUrl(source.url),
};
}
}) ?? []
);
};
const handleRange = (
rawRange: CollectibleBlock["range"]
): Extract<EndpointSource, { type: "collectible" }>["range"] => {
const range = rawRange?.[0];
switch (range?.blockType) {
case "page":
return { type: "page", page: range.page };
case "timestamp":
return { type: "timestamp", timestamp: range.timestamp };
case "other":
return {
type: "custom",
translations: range.translations.map(({ language, note }) => ({
language: isPayloadType(language) ? language.id : language,
note,
})),
};
case undefined:
default:
return undefined;
}
};

@ -0,0 +1,35 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { CollectionEndpoint } from "../../../types/payload";
import { eventToEndpointEvent } from "./getAllEndpoint";
export const getByID: CollectionEndpoint = {
method: "get",
path: "/:id",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
if (!req.params.id) {
return res.status(400).send({ errors: [{ message: "Missing 'id' query params" }] });
}
try {
const result = await payload.findByID({
collection: Collections.ChronologyEvents,
id: req.params.id,
});
return res.status(200).json(eventToEndpointEvent(result));
} catch {
return res.sendStatus(404);
}
},
};

@ -1,7 +1,8 @@
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { StrapiLanguage } from "../../../types/strapi";
import { isUndefined } from "../../../utils/asserts";
import { isDefined, isUndefined } from "../../../utils/asserts";
import { plainTextToLexical } from "../../../utils/string";
type StrapiChronologyItem = {
year: number;
@ -25,20 +26,25 @@ export const importFromStrapi = createStrapiImportEndpoint<StrapiChronologyItem>
},
},
payload: {
collection: Collections.ChronologyItems,
collection: Collections.ChronologyEvents,
convert: ({ year, month, day, events }, user) => ({
date: { year, month, day },
events: events.map((event) => ({
translations: event.translations.map(({ title, description, note, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a chronology item event translation");
const newLanguage =
language.data.attributes.code === "pt-br" ? "pt" : language.data.attributes.code;
const sourceLanguage = language.data.attributes.code === "ja" ? "ja" : "en";
return {
title,
description,
note,
language: language.data.attributes.code,
sourceLanguage: "en",
...(language.data.attributes.code === "en"
...(isDefined(title) ? { title } : {}),
...(isDefined(note) ? { notes: plainTextToLexical(note) } : {}),
...(isDefined(description) ? { description: plainTextToLexical(description) } : {}),
language: newLanguage,
sourceLanguage,
...(newLanguage === sourceLanguage
? { transcribers: [user.id] }
: { translators: [user.id] }),
};

@ -1,11 +1,11 @@
import { FieldHook } from "payload/dist/fields/config/types";
import { ChronologyItem } from "../../../types/collections";
import { ChronologyEvent } from "../../../types/collections";
import { isDefined, isUndefined } from "../../../utils/asserts";
export const beforeValidatePopulateNameField: FieldHook<
ChronologyItem,
ChronologyItem["name"],
ChronologyItem
ChronologyEvent,
ChronologyEvent["name"],
ChronologyEvent
> = ({ data }) => {
if (isUndefined(data) || isUndefined(data.date) || isUndefined(data.date.year))
return "????-??-??";

@ -1,9 +1,9 @@
import { DateTime } from "luxon";
import { Validate } from "payload/types";
import { ChronologyItem } from "../../../types/collections";
import { ChronologyEvent } from "../../../types/collections";
import { isUndefined } from "../../../utils/asserts";
export const validateDate: Validate<ChronologyItem["date"] | undefined> = (date) => {
export const validateDate: Validate<ChronologyEvent["date"] | undefined> = (date) => {
if (isUndefined(date)) return "This field is required.";
const { year, month, day } = date;
if (isUndefined(day)) return true;

@ -1,11 +1,11 @@
import { Validate } from "payload/types";
import { ChronologyItem } from "../../../types/collections";
import { ChronologyEvent } from "../../../types/collections";
import { isEmpty } from "../../../utils/asserts";
export const validateEventsTranslationsDescription: Validate<
string | undefined,
ChronologyItem,
ChronologyItem["events"][number]["translations"][number],
ChronologyEvent,
ChronologyEvent["events"][number]["translations"][number],
unknown
> = (_, { siblingData: { description, title } }) => {
if (!description && isEmpty(title)) {

@ -1,11 +1,11 @@
import { Validate } from "payload/types";
import { ChronologyItem } from "../../../types/collections";
import { ChronologyEvent } from "../../../types/collections";
import { isEmpty } from "../../../utils/asserts";
export const validateEventsTranslationsTitle: Validate<
string | undefined,
ChronologyItem,
ChronologyItem["events"][number]["translations"][number],
ChronologyEvent,
ChronologyEvent["events"][number]["translations"][number],
unknown
> = (_, { siblingData: { description, title } }) => {
if (!description && isEmpty(title)) {

@ -8,7 +8,7 @@ import {
isPayloadType,
isValidPayloadImage,
} from "../../../utils/asserts";
import { convertTagsToGroups, handleParentPages } from "../../../utils/endpoints";
import { convertTagsToGroups, getDomainFromUrl, handleParentPages } from "../../../utils/endpoints";
import { convertPageToPreview } from "../../Pages/endpoints/getBySlugEndpoint";
export const getBySlugEndpoint = createGetByEndpoint(
@ -41,9 +41,9 @@ export const getBySlugEndpoint = createGetByEndpoint(
gallery: handleGallery(gallery),
scans: handleScans(collectible.scans),
nature: nature === "Physical" ? CollectibleNature.Physical : CollectibleNature.Digital,
parentPages: handleParentPages({collectibles: parentItems, folders}),
parentPages: handleParentPages({ collectibles: parentItems, folders }),
subitems: isPayloadArrayType(subitems) ? subitems.map(convertCollectibleToPreview) : [],
urls: urls?.map(({ url }) => ({ url, label: getLabelFromUrl(url) })) ?? [],
urls: urls?.map(({ url }) => ({ url, label: getDomainFromUrl(url) })) ?? [],
...(weightEnabled && weight ? { weight: weight.amount } : {}),
...handleSize(size, sizeEnabled),
...handlePageInfo(pageInfo, pageInfoEnabled),
@ -53,7 +53,7 @@ export const getBySlugEndpoint = createGetByEndpoint(
3
);
export const handlePrice = (
const handlePrice = (
price: Collectible["price"],
enabled: Collectible["priceEnabled"]
): { price: NonNullable<EndpointCollectible["price"]> } | {} => {
@ -63,7 +63,7 @@ export const handlePrice = (
};
};
export const handleSize = (
const handleSize = (
size: Collectible["size"],
enabled: Collectible["sizeEnabled"]
): { size: NonNullable<EndpointCollectible["size"]> } | {} => {
@ -77,7 +77,7 @@ export const handleSize = (
};
};
export const handlePageInfo = (
const handlePageInfo = (
pageInfo: Collectible["pageInfo"],
enabled: Collectible["pageInfoEnabled"]
): { pageInfo: NonNullable<EndpointCollectible["pageInfo"]> } | {} => {
@ -91,7 +91,7 @@ export const handlePageInfo = (
};
};
export const handleGallery = (gallery: Collectible["gallery"]): EndpointCollectible["gallery"] => {
const handleGallery = (gallery: Collectible["gallery"]): EndpointCollectible["gallery"] => {
const result: PayloadImage[] = [];
if (!gallery) return result;
@ -104,7 +104,7 @@ export const handleGallery = (gallery: Collectible["gallery"]): EndpointCollecti
return result.slice(0, 10);
};
export const handleScans = (scans: Collectible["scans"]): EndpointCollectible["scans"] => {
const handleScans = (scans: Collectible["scans"]): EndpointCollectible["scans"] => {
const result: PayloadImage[] = [];
if (!scans) return result;
@ -133,9 +133,7 @@ export const handleScans = (scans: Collectible["scans"]): EndpointCollectible["s
return result.slice(0, 10);
};
export const handleContents = (
contents: Collectible["contents"]
): EndpointCollectible["contents"] => {
const handleContents = (contents: Collectible["contents"]): EndpointCollectible["contents"] => {
if (!contents) return [];
return contents.flatMap(({ content, range: rangeArray }) => {
@ -229,12 +227,3 @@ export const convertCollectibleToPreview = ({
})) ?? [],
};
};
const getLabelFromUrl = (url: string): string => {
const urlObject = new URL(url);
let domain = urlObject.hostname;
if (domain.startsWith("www.")) {
domain = domain.substring("www.".length);
}
return domain;
};

@ -7,7 +7,6 @@ import { translatedFields } from "../../fields/translatedFields/translatedFields
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor";
import { getBySlugEndpoint } from "./endpoints/getBySlugEndpoint";
import { getRootFoldersEndpoint } from "./endpoints/rootEndpoint";
const fields = {
slug: "slug",
@ -34,7 +33,7 @@ export const Folders = buildCollectionConfig({
"Folders provide a way to structure our content. A folder can contain subfolders and/or files.",
preview: ({ slug }) => `https://v3.accords-library.com/en/folders/${slug}`,
},
endpoints: [getRootFoldersEndpoint, getBySlugEndpoint],
endpoints: [getBySlugEndpoint],
fields: [
rowField([
slugField({ name: fields.slug }),

@ -1,52 +0,0 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { Folder } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isPayloadType } from "../../../utils/asserts";
import { convertFolderToPreview } from "./getBySlugEndpoint";
export const getRootFoldersEndpoint: CollectionEndpoint = {
method: "get",
path: "/root",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
const homeFolder = (
await payload.find({
collection: Collections.Folders,
limit: 100,
where: { slug: { equals: "home" } },
})
).docs[0];
if (!homeFolder) {
res.status(404);
return;
}
const folders = homeFolder.sections?.[0]?.subfolders;
if (!folders) {
res.status(404);
return;
}
const result = folders.filter(isPayloadType).filter(isEmptyFolder).map(convertFolderToPreview);
res.status(200).json(result);
},
};
const isEmptyFolder = ({ sections, files }: Folder): boolean => {
if (sections && sections.length > 0) return true;
if (files && files.length > 0) return true;
return false;
};

@ -3,8 +3,7 @@ import type { BreakBlock, Image, SectionBlock, TranscriptBlock } from "./types/c
// END MOCKING SECTION
export enum Collections {
ChronologyEras = "chronology-eras",
ChronologyItems = "chronology-items",
ChronologyEvents = "chronology-events",
Currencies = "currencies",
Files = "files",
Languages = "languages",

@ -3,8 +3,7 @@ import { mongooseAdapter } from "@payloadcms/db-mongodb";
import path from "path";
import { buildConfig } from "payload/config";
import { BackgroundImages } from "./collections/BackgroundImages/BackgroundImages";
import { ChronologyEras } from "./collections/ChronologyEras/ChronologyEras";
import { ChronologyItems } from "./collections/ChronologyItems/ChronologyItems";
import { ChronologyEvents } from "./collections/ChronologyEvents/ChronologyEvents";
import { Collectibles } from "./collections/Collectibles/Collectibles";
import { Currencies } from "./collections/Currencies/Currencies";
import { Folders } from "./collections/Folders/Folders";
@ -44,16 +43,15 @@ export default buildConfig({
Pages,
Collectibles,
Folders,
ChronologyItems,
ChronologyEras,
ChronologyEvents,
Notes,
Images,
BackgroundImages,
RecordersThumbnails,
Videos,
VideosChannels,
Tags,
TagsGroups,
Recorders,

@ -320,6 +320,40 @@ export type TableOfContentEntry = {
children: TableOfContentEntry[];
};
export type EndpointChronologyEvent = {
id: string;
date: {
year: number;
month?: number;
day?: number;
};
events: {
sources: EndpointSource[];
translations: {
language: string;
sourceLanguage: string;
title?: string;
description?: RichTextContent;
notes?: RichTextContent;
transcribers: EndpointRecorder[];
translators: EndpointRecorder[];
proofreaders: EndpointRecorder[];
}[];
}[];
};
export type EndpointSource =
| { type: "url"; url: string; label: string }
| {
type: "collectible";
collectible: EndpointCollectiblePreview;
range?:
| { type: "page"; page: number }
| { type: "timestamp"; timestamp: string }
| { type: "custom"; translations: { language: string; note: string }[] };
}
| { type: "page"; page: EndpointPagePreview };
export type PayloadImage = {
url: string;
width: number;
@ -329,8 +363,6 @@ export type PayloadImage = {
};
export const payload = {
getEras: async (): Promise<EndpointEra[]> =>
await (await request(payloadApiUrl(Collections.ChronologyEras, `all`))).json(),
getHomeFolders: async (): Promise<EndpointHomeFolder[]> =>
await (await request(payloadApiUrl(Collections.HomeFolders, `all`, true))).json(),
getFolder: async (slug: string): Promise<EndpointFolder> =>
@ -347,4 +379,8 @@ export const payload = {
await (await request(payloadApiUrl(Collections.Pages, `slug/${slug}`))).json(),
getCollectible: async (slug: string): Promise<EndpointCollectible> =>
await (await request(payloadApiUrl(Collections.Collectibles, `slug/${slug}`))).json(),
getChronologyEvents: async (): Promise<EndpointChronologyEvent[]> =>
await (await request(payloadApiUrl(Collections.ChronologyEvents, `all`))).json(),
getChronologyEventByID: async (id: string): Promise<EndpointChronologyEvent> =>
await (await request(payloadApiUrl(Collections.ChronologyEvents, id))).json(),
};

@ -77,10 +77,28 @@ html[data-theme="light"] {
.blocks-field__block-pill-cueBlock + .section-title,
.blocks-field__block-pill-pageRange + .section-title,
.blocks-field__block-pill-timeRange + .section-title,
.blocks-field__block-pill-urlBlock + .section-title,
.blocks-field__block-pill-pageBlock + .section-title,
.blocks-field__block-pill-collectibleBlock + .section-title,
.blocks-field__block-pill-page + .section-title,
.blocks-field__block-pill-timestamp + .section-title,
.blocks-field__block-pill-other + .section-title,
.blocks-field__block-pill-other + .section-title {
display: none;
}
.collapsible__toggle-wrap:has(
.blocks-field__block-pill-collectibleBlock,
.blocks-field__block-pill-page,
.blocks-field__block-pill-timestamp,
.blocks-field__block-pill-other,
.blocks-field__block-pill-pageRange,
.blocks-field__block-pill-timeRange
)
.blocks-field__block-number {
display: none;
}
// Reduce margin on Lexical blocks with the classname "reduced-margins"
.rich-text-lexical.field-type.reduced-margins {

@ -100,3 +100,12 @@ export const handleRecorder = ({
username: anonymize ? `Recorder#${id.substring(0, 5)}` : username,
...(isValidPayloadImage(avatar) ? { avatar } : {}),
});
export const getDomainFromUrl = (url: string): string => {
const urlObject = new URL(url);
let domain = urlObject.hostname;
if (domain.startsWith("www.")) {
domain = domain.substring("www.".length);
}
return domain;
};