diff --git a/src/collections/ChronologyEras/ChronologyEras.ts b/src/collections/ChronologyEras/ChronologyEras.ts deleted file mode 100644 index 86897f0..0000000 --- a/src/collections/ChronologyEras/ChronologyEras.ts +++ /dev/null @@ -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; - -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 } }, - ], - }), - }), - ], -}); diff --git a/src/collections/ChronologyEras/endpoints/getAllEndpoint.ts b/src/collections/ChronologyEras/endpoints/getAllEndpoint.ts deleted file mode 100644 index 2834811..0000000 --- a/src/collections/ChronologyEras/endpoints/getAllEndpoint.ts +++ /dev/null @@ -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( - ({ 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) - .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); - }, -}; diff --git a/src/collections/ChronologyEras/endpoints/importFromStrapi.ts b/src/collections/ChronologyEras/endpoints/importFromStrapi.ts deleted file mode 100644 index 87bb965..0000000 --- a/src/collections/ChronologyEras/endpoints/importFromStrapi.ts +++ /dev/null @@ -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({ - 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) } : {}), - }; - }), - }), - }, -}); diff --git a/src/collections/ChronologyEras/hooks/beforeValidateEndingGreaterThanStarting.ts b/src/collections/ChronologyEras/hooks/beforeValidateEndingGreaterThanStarting.ts deleted file mode 100644 index ac61b0c..0000000 --- a/src/collections/ChronologyEras/hooks/beforeValidateEndingGreaterThanStarting.ts +++ /dev/null @@ -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."); - } -}; diff --git a/src/collections/ChronologyEras/hooks/beforeValidateNoIntersection.ts b/src/collections/ChronologyEras/hooks/beforeValidateNoIntersection.ts deleted file mode 100644 index 6b2da27..0000000 --- a/src/collections/ChronologyEras/hooks/beforeValidateNoIntersection.ts +++ /dev/null @@ -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 = 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})` - ); - } - }); -}; diff --git a/src/collections/ChronologyItems/ChronologyItems.ts b/src/collections/ChronologyEvents/ChronologyEvents.ts similarity index 78% rename from src/collections/ChronologyItems/ChronologyItems.ts rename to src/collections/ChronologyEvents/ChronologyEvents.ts index 49adf5d..e8ef299 100644 --- a/src/collections/ChronologyItems/ChronologyItems.ts +++ b/src/collections/ChronologyEvents/ChronologyEvents.ts @@ -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; -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, diff --git a/src/collections/ChronologyEvents/blocks/collectibleBlock.ts b/src/collections/ChronologyEvents/blocks/collectibleBlock.ts new file mode 100644 index 0000000..850a3d0 --- /dev/null +++ b/src/collections/ChronologyEvents/blocks/collectibleBlock.ts @@ -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, + }, + ], + }), + ], + }, + ], + }, + ], +}; diff --git a/src/collections/ChronologyEvents/blocks/contentBlock.ts b/src/collections/ChronologyEvents/blocks/contentBlock.ts new file mode 100644 index 0000000..ade726d --- /dev/null +++ b/src/collections/ChronologyEvents/blocks/contentBlock.ts @@ -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, + }, + ], +}; diff --git a/src/collections/ChronologyEvents/blocks/urlBlock.ts b/src/collections/ChronologyEvents/blocks/urlBlock.ts new file mode 100644 index 0000000..d5a6b98 --- /dev/null +++ b/src/collections/ChronologyEvents/blocks/urlBlock.ts @@ -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 }], +}; diff --git a/src/collections/ChronologyEvents/endpoints/getAllEndpoint.ts b/src/collections/ChronologyEvents/endpoints/getAllEndpoint.ts new file mode 100644 index 0000000..b4b0924 --- /dev/null +++ b/src/collections/ChronologyEvents/endpoints/getAllEndpoint.ts @@ -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(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(({ 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((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["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; + } +}; diff --git a/src/collections/ChronologyEvents/endpoints/getByID.ts b/src/collections/ChronologyEvents/endpoints/getByID.ts new file mode 100644 index 0000000..e4d5b7a --- /dev/null +++ b/src/collections/ChronologyEvents/endpoints/getByID.ts @@ -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); + } + }, +}; diff --git a/src/collections/ChronologyItems/endpoints/importFromStrapi.ts b/src/collections/ChronologyEvents/endpoints/importFromStrapi.ts similarity index 62% rename from src/collections/ChronologyItems/endpoints/importFromStrapi.ts rename to src/collections/ChronologyEvents/endpoints/importFromStrapi.ts index 51878fc..d1d9f3a 100644 --- a/src/collections/ChronologyItems/endpoints/importFromStrapi.ts +++ b/src/collections/ChronologyEvents/endpoints/importFromStrapi.ts @@ -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 }, }, 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] }), }; diff --git a/src/collections/ChronologyItems/hooks/beforeValidatePopulateNameField.ts b/src/collections/ChronologyEvents/hooks/beforeValidatePopulateNameField.ts similarity index 82% rename from src/collections/ChronologyItems/hooks/beforeValidatePopulateNameField.ts rename to src/collections/ChronologyEvents/hooks/beforeValidatePopulateNameField.ts index 164d159..9192965 100644 --- a/src/collections/ChronologyItems/hooks/beforeValidatePopulateNameField.ts +++ b/src/collections/ChronologyEvents/hooks/beforeValidatePopulateNameField.ts @@ -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 "????-??-??"; diff --git a/src/collections/ChronologyItems/validations/validateDate.ts b/src/collections/ChronologyEvents/validations/validateDate.ts similarity index 78% rename from src/collections/ChronologyItems/validations/validateDate.ts rename to src/collections/ChronologyEvents/validations/validateDate.ts index 1f2798f..675c03e 100644 --- a/src/collections/ChronologyItems/validations/validateDate.ts +++ b/src/collections/ChronologyEvents/validations/validateDate.ts @@ -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 = (date) => { +export const validateDate: Validate = (date) => { if (isUndefined(date)) return "This field is required."; const { year, month, day } = date; if (isUndefined(day)) return true; diff --git a/src/collections/ChronologyItems/validations/validateEventsTranslationsDescription.ts b/src/collections/ChronologyEvents/validations/validateEventsTranslationsDescription.ts similarity index 71% rename from src/collections/ChronologyItems/validations/validateEventsTranslationsDescription.ts rename to src/collections/ChronologyEvents/validations/validateEventsTranslationsDescription.ts index 210e21b..e9cf957 100644 --- a/src/collections/ChronologyItems/validations/validateEventsTranslationsDescription.ts +++ b/src/collections/ChronologyEvents/validations/validateEventsTranslationsDescription.ts @@ -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)) { diff --git a/src/collections/ChronologyItems/validations/validateEventsTranslationsTitle.ts b/src/collections/ChronologyEvents/validations/validateEventsTranslationsTitle.ts similarity index 71% rename from src/collections/ChronologyItems/validations/validateEventsTranslationsTitle.ts rename to src/collections/ChronologyEvents/validations/validateEventsTranslationsTitle.ts index e7df06f..54ea888 100644 --- a/src/collections/ChronologyItems/validations/validateEventsTranslationsTitle.ts +++ b/src/collections/ChronologyEvents/validations/validateEventsTranslationsTitle.ts @@ -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)) { diff --git a/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts b/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts index 2ab37b4..d149537 100644 --- a/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts +++ b/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts @@ -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 } | {} => { @@ -63,7 +63,7 @@ export const handlePrice = ( }; }; -export const handleSize = ( +const handleSize = ( size: Collectible["size"], enabled: Collectible["sizeEnabled"] ): { size: NonNullable } | {} => { @@ -77,7 +77,7 @@ export const handleSize = ( }; }; -export const handlePageInfo = ( +const handlePageInfo = ( pageInfo: Collectible["pageInfo"], enabled: Collectible["pageInfoEnabled"] ): { pageInfo: NonNullable } | {} => { @@ -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; -}; diff --git a/src/collections/Folders/Folders.ts b/src/collections/Folders/Folders.ts index 0484b89..510e513 100644 --- a/src/collections/Folders/Folders.ts +++ b/src/collections/Folders/Folders.ts @@ -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 }), diff --git a/src/collections/Folders/endpoints/rootEndpoint.ts b/src/collections/Folders/endpoints/rootEndpoint.ts deleted file mode 100644 index 609b425..0000000 --- a/src/collections/Folders/endpoints/rootEndpoint.ts +++ /dev/null @@ -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; -}; diff --git a/src/constants.ts b/src/constants.ts index 7551ef0..d6de9de 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -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", diff --git a/src/payload.config.ts b/src/payload.config.ts index 2e1ccbf..16cf6de 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -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, diff --git a/src/sdk.ts b/src/sdk.ts index daf7b69..44ebdf8 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -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 => - await (await request(payloadApiUrl(Collections.ChronologyEras, `all`))).json(), getHomeFolders: async (): Promise => await (await request(payloadApiUrl(Collections.HomeFolders, `all`, true))).json(), getFolder: async (slug: string): Promise => @@ -347,4 +379,8 @@ export const payload = { await (await request(payloadApiUrl(Collections.Pages, `slug/${slug}`))).json(), getCollectible: async (slug: string): Promise => await (await request(payloadApiUrl(Collections.Collectibles, `slug/${slug}`))).json(), + getChronologyEvents: async (): Promise => + await (await request(payloadApiUrl(Collections.ChronologyEvents, `all`))).json(), + getChronologyEventByID: async (id: string): Promise => + await (await request(payloadApiUrl(Collections.ChronologyEvents, id))).json(), }; diff --git a/src/styles.scss b/src/styles.scss index f95aa3a..4502b0f 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -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 { diff --git a/src/utils/endpoints.ts b/src/utils/endpoints.ts index 7fa19a4..de7a6e3 100644 --- a/src/utils/endpoints.ts +++ b/src/utils/endpoints.ts @@ -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; +};