From 46613c85006f880d9150ede19611b98193bc8bb3 Mon Sep 17 00:00:00 2001 From: DrMint <29893320+DrMint@users.noreply.github.com> Date: Wed, 15 May 2024 13:44:50 +0200 Subject: [PATCH] Use attributes instead of group tags --- src/blocks/attributeBlocks/numberBlock.ts | 25 ++++ src/blocks/attributeBlocks/tagsBlock.ts | 28 +++++ src/blocks/attributeBlocks/textBlock.ts | 25 ++++ src/collections/Attributes/Attributes.ts | 51 ++++++++ src/collections/Audios/Audios.ts | 3 + src/collections/Audios/endpoints/getByID.ts | 6 +- src/collections/Collectibles/Collectibles.ts | 3 + .../endpoints/getBySlugEndpoint.ts | 6 +- src/collections/Images/Images.ts | 3 + src/collections/Images/endpoints/getByID.ts | 6 +- .../MediaThumbnails.ts} | 0 src/collections/Pages/Pages.ts | 3 + .../Pages/endpoints/getBySlugEndpoint.ts | 6 +- src/collections/Tags/Tags.ts | 53 +++----- src/collections/TagsGroups/TagsGroups.ts | 34 ----- src/collections/Videos/Videos.ts | 3 + src/collections/Videos/endpoints/getByID.ts | 6 +- src/constants.ts | 32 +++-- src/fields/attributesField/attributesField.ts | 12 ++ src/payload.config.ts | 6 +- src/sdk.ts | 31 ++++- src/styles.scss | 3 + src/types/collections.ts | 48 ++++++- src/utils/endpoints.ts | 119 ++++++++++++------ 24 files changed, 363 insertions(+), 149 deletions(-) create mode 100644 src/blocks/attributeBlocks/numberBlock.ts create mode 100644 src/blocks/attributeBlocks/tagsBlock.ts create mode 100644 src/blocks/attributeBlocks/textBlock.ts create mode 100644 src/collections/Attributes/Attributes.ts rename src/collections/{VideosThumbnails/VideosThumbnails.ts => MediaThumbnails/MediaThumbnails.ts} (100%) delete mode 100644 src/collections/TagsGroups/TagsGroups.ts create mode 100644 src/fields/attributesField/attributesField.ts diff --git a/src/blocks/attributeBlocks/numberBlock.ts b/src/blocks/attributeBlocks/numberBlock.ts new file mode 100644 index 0000000..b0e45d4 --- /dev/null +++ b/src/blocks/attributeBlocks/numberBlock.ts @@ -0,0 +1,25 @@ +import { Block } from "payload/types"; +import { AttributeTypes, Collections } from "../../constants"; +import { rowField } from "../../fields/rowField/rowField"; + +export const numberBlock: Block = { + slug: "numberBlock", + interfaceName: "NumberBlock", + labels: { singular: "Number attribute", plural: "Number attributes" }, + fields: [ + rowField([ + { + name: "name", + type: "relationship", + relationTo: Collections.Attributes, + filterOptions: () => ({ type: { equals: AttributeTypes.Number } }), + required: true, + }, + { + name: "number", + type: "number", + required: true, + }, + ]), + ], +}; diff --git a/src/blocks/attributeBlocks/tagsBlock.ts b/src/blocks/attributeBlocks/tagsBlock.ts new file mode 100644 index 0000000..87dc406 --- /dev/null +++ b/src/blocks/attributeBlocks/tagsBlock.ts @@ -0,0 +1,28 @@ +import { Block } from "payload/types"; +import { AttributeTypes, Collections } from "../../constants"; +import { rowField } from "../../fields/rowField/rowField"; + +export const tagsBlock: Block = { + slug: "tagsBlock", + interfaceName: "TagsBlock", + labels: { singular: "Tags attribute", plural: "Tags attributes" }, + fields: [ + rowField([ + { + name: "name", + type: "relationship", + relationTo: Collections.Attributes, + filterOptions: () => ({ type: { equals: AttributeTypes.Tags } }), + required: true, + }, + { + name: "tags", + type: "relationship", + relationTo: Collections.Tags, + required: true, + hasMany: true, + minRows: 1, + }, + ]), + ], +}; diff --git a/src/blocks/attributeBlocks/textBlock.ts b/src/blocks/attributeBlocks/textBlock.ts new file mode 100644 index 0000000..b006179 --- /dev/null +++ b/src/blocks/attributeBlocks/textBlock.ts @@ -0,0 +1,25 @@ +import { Block } from "payload/types"; +import { AttributeTypes, Collections } from "../../constants"; +import { rowField } from "../../fields/rowField/rowField"; + +export const textBlock: Block = { + slug: "textBlock", + interfaceName: "TextBlock", + labels: { singular: "Text attribute", plural: "Text attributes" }, + fields: [ + rowField([ + { + name: "name", + type: "relationship", + relationTo: Collections.Attributes, + filterOptions: () => ({ type: { equals: AttributeTypes.Text } }), + required: true, + }, + { + name: "text", + type: "textarea", + required: true, + }, + ]), + ], +}; diff --git a/src/collections/Attributes/Attributes.ts b/src/collections/Attributes/Attributes.ts new file mode 100644 index 0000000..3019c67 --- /dev/null +++ b/src/collections/Attributes/Attributes.ts @@ -0,0 +1,51 @@ +import { CollectionConfig } from "payload/types"; +import { mustBeAdmin } from "../../accesses/fields/mustBeAdmin"; +import { AttributeTypes, CollectionGroups, Collections } from "../../constants"; +import { iconField } from "../../fields/iconField/iconField"; +import { rowField } from "../../fields/rowField/rowField"; +import { slugField } from "../../fields/slugField/slugField"; +import { translatedFields } from "../../fields/translatedFields/translatedFields"; +import { buildCollectionConfig } from "../../utils/collectionConfig"; + +const fields = { + slug: "slug", + translations: "translations", + translationsName: "name", + icon: "icon", + type: "type", +}; + +export const Attributes: CollectionConfig = buildCollectionConfig({ + slug: Collections.Attributes, + labels: { singular: "Attribute", plural: "Attributes" }, + admin: { + group: CollectionGroups.Meta, + useAsTitle: fields.slug, + defaultColumns: [fields.slug, fields.icon, fields.type, fields.translations], + }, + fields: [ + rowField([ + slugField({ name: fields.slug }), + iconField({ name: fields.icon }), + { + name: fields.type, + type: "select", + access: { + update: mustBeAdmin, + }, + required: true, + options: Object.values(AttributeTypes).map((value) => ({ + label: value, + value: value, + })), + }, + ]), + translatedFields({ + name: fields.translations, + admin: { useAsTitle: fields.translationsName }, + required: true, + minRows: 1, + fields: [{ name: fields.translationsName, type: "text", required: true }], + }), + ], +}); diff --git a/src/collections/Audios/Audios.ts b/src/collections/Audios/Audios.ts index 0da2804..f746d24 100644 --- a/src/collections/Audios/Audios.ts +++ b/src/collections/Audios/Audios.ts @@ -1,4 +1,5 @@ import { CollectionGroups, Collections } from "../../constants"; +import { attributesField } from "../../fields/attributesField/attributesField"; import { creditsField } from "../../fields/creditsField/creditsField"; import { imageField } from "../../fields/imageField/imageField"; import { rowField } from "../../fields/rowField/rowField"; @@ -21,6 +22,7 @@ const fields = { thumbnail: "thumbnail", duration: "duration", tags: "tags", + attributes: "attributes", credits: "credits", }; @@ -72,6 +74,7 @@ export const Audios = buildCollectionConfig({ ], }), tagsField({ name: fields.tags }), + attributesField({ name: fields.attributes }), creditsField({ name: fields.credits }), ], }); diff --git a/src/collections/Audios/endpoints/getByID.ts b/src/collections/Audios/endpoints/getByID.ts index 2a3a8f9..32255e3 100644 --- a/src/collections/Audios/endpoints/getByID.ts +++ b/src/collections/Audios/endpoints/getByID.ts @@ -5,9 +5,9 @@ import { Audio } from "../../../types/collections"; import { CollectionEndpoint } from "../../../types/payload"; import { isNotEmpty, isValidPayloadImage, isValidPayloadMedia } from "../../../utils/asserts"; import { + convertAttributesToEndpointAttributes, convertCreditsToEndpointCredits, convertRTCToEndpointRTC, - convertTagsEndpointTagsGroups, getLanguageId, } from "../../../utils/endpoints"; @@ -48,7 +48,7 @@ export const getByID: CollectionEndpoint = { export const convertAudioToEndpointAudio = ({ url, - tags, + attributes, translations, mimeType, createdAt, @@ -61,7 +61,7 @@ export const convertAudioToEndpointAudio = ({ credits, }: Audio & PayloadMedia): EndpointAudio => ({ url, - tagGroups: convertTagsEndpointTagsGroups(tags), + attributes: convertAttributesToEndpointAttributes(attributes), createdAt, filename, filesize, diff --git a/src/collections/Collectibles/Collectibles.ts b/src/collections/Collectibles/Collectibles.ts index cc58cbb..b387c93 100644 --- a/src/collections/Collectibles/Collectibles.ts +++ b/src/collections/Collectibles/Collectibles.ts @@ -7,6 +7,7 @@ import { CollectionGroups, Collections, } from "../../constants"; +import { attributesField } from "../../fields/attributesField/attributesField"; import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { componentField } from "../../fields/componentField/componentField"; import { creditsField } from "../../fields/creditsField/creditsField"; @@ -34,6 +35,7 @@ const fields = { backgroundImage: "backgroundImage", nature: "nature", tags: "tags", + attributes: "attributes", languages: "languages", translations: "translations", @@ -189,6 +191,7 @@ export const Collectibles = buildVersionedCollectionConfig({ ]), tagsField({ name: fields.tags }), + attributesField({ name: fields.attributes }), translatedFields({ name: fields.translations, diff --git a/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts b/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts index 17895cd..4a42bde 100644 --- a/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts +++ b/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts @@ -12,8 +12,8 @@ import { isValidPayloadMedia, } from "../../../utils/asserts"; import { + convertAttributesToEndpointAttributes, convertSourceToEndpointSource, - convertTagsEndpointTagsGroups, getDomainFromUrl, } from "../../../utils/endpoints"; import { convertAudioToEndpointAudio } from "../../Audios/endpoints/getByID"; @@ -52,7 +52,7 @@ export const convertCollectibleToEndpointCollectible = ({ releaseDate, languages, scans: rawScans, - tags, + attributes, createdAt, updatedAt, scansEnabled, @@ -72,7 +72,7 @@ export const convertCollectibleToEndpointCollectible = ({ ...(isValidPayloadImage(backgroundImage) ? { backgroundImage: convertImageToEndpointImage(backgroundImage) } : {}), - tagGroups: convertTagsEndpointTagsGroups(tags), + attributes: convertAttributesToEndpointAttributes(attributes), translations: translations?.map(({ language, title, description, pretitle, subtitle }) => ({ language: isPayloadType(language) ? language.id : language, diff --git a/src/collections/Images/Images.ts b/src/collections/Images/Images.ts index 25bfc6e..f87991b 100644 --- a/src/collections/Images/Images.ts +++ b/src/collections/Images/Images.ts @@ -1,4 +1,5 @@ import { Collections } from "../../constants"; +import { attributesField } from "../../fields/attributesField/attributesField"; import { creditsField } from "../../fields/creditsField/creditsField"; import { rowField } from "../../fields/rowField/rowField"; import { tagsField } from "../../fields/tagsField/tagsField"; @@ -19,6 +20,7 @@ const fields = { translationsSubtitle: "subtitle", translationsDescription: "description", tags: "tags", + attributes: "attributes", credits: "credits", } as const satisfies Record; @@ -64,6 +66,7 @@ export const Images = buildImageCollectionConfig({ ], }), tagsField({ name: fields.tags }), + attributesField({ name: fields.attributes }), creditsField({ name: fields.credits }), ], }); diff --git a/src/collections/Images/endpoints/getByID.ts b/src/collections/Images/endpoints/getByID.ts index 740d163..472fae9 100644 --- a/src/collections/Images/endpoints/getByID.ts +++ b/src/collections/Images/endpoints/getByID.ts @@ -5,9 +5,9 @@ import { Image } from "../../../types/collections"; import { CollectionEndpoint } from "../../../types/payload"; import { isNotEmpty, isValidPayloadImage } from "../../../utils/asserts"; import { + convertAttributesToEndpointAttributes, convertCreditsToEndpointCredits, convertRTCToEndpointRTC, - convertTagsEndpointTagsGroups, getLanguageId, } from "../../../utils/endpoints"; @@ -50,7 +50,7 @@ export const convertImageToEndpointImage = ({ url, width, height, - tags, + attributes, translations, mimeType, createdAt, @@ -63,7 +63,7 @@ export const convertImageToEndpointImage = ({ url, width, height, - tagGroups: convertTagsEndpointTagsGroups(tags), + attributes: convertAttributesToEndpointAttributes(attributes), createdAt, filename, filesize, diff --git a/src/collections/VideosThumbnails/VideosThumbnails.ts b/src/collections/MediaThumbnails/MediaThumbnails.ts similarity index 100% rename from src/collections/VideosThumbnails/VideosThumbnails.ts rename to src/collections/MediaThumbnails/MediaThumbnails.ts diff --git a/src/collections/Pages/Pages.ts b/src/collections/Pages/Pages.ts index 8e97b68..394eda4 100644 --- a/src/collections/Pages/Pages.ts +++ b/src/collections/Pages/Pages.ts @@ -4,6 +4,7 @@ import { sectionBlock } from "../../blocks/sectionBlock"; import { transcriptBlock } from "../../blocks/transcriptBlock"; import { QuickFilters, publishStatusFilters } from "../../components/QuickFilters"; import { CollectionGroups, Collections } from "../../constants"; +import { attributesField } from "../../fields/attributesField/attributesField"; import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { creditsField } from "../../fields/creditsField/creditsField"; import { imageField } from "../../fields/imageField/imageField"; @@ -24,6 +25,7 @@ const fields = { backgroundImage: "backgroundImage", translations: "translations", tags: "tags", + attributes: "attributes", sourceLanguage: "sourceLanguage", pretitle: "pretitle", title: "title", @@ -89,6 +91,7 @@ export const Pages = buildVersionedCollectionConfig({ }), ]), tagsField({ name: fields.tags }), + attributesField({ name: fields.attributes }), translatedFields({ name: fields.translations, admin: { useAsTitle: fields.title, hasSourceLanguage: true }, diff --git a/src/collections/Pages/endpoints/getBySlugEndpoint.ts b/src/collections/Pages/endpoints/getBySlugEndpoint.ts index f20188f..216ed4b 100644 --- a/src/collections/Pages/endpoints/getBySlugEndpoint.ts +++ b/src/collections/Pages/endpoints/getBySlugEndpoint.ts @@ -11,10 +11,10 @@ import { EndpointPage, TableOfContentEntry } from "../../../sdk"; import { Page } from "../../../types/collections"; import { isNotEmpty, isPayloadType, isValidPayloadImage } from "../../../utils/asserts"; import { + convertAttributesToEndpointAttributes, convertCreditsToEndpointCredits, convertRTCToEndpointRTC, convertSourceToEndpointSource, - convertTagsEndpointTagsGroups, } from "../../../utils/endpoints"; import { convertImageToEndpointImage } from "../../Images/endpoints/getByID"; import { convertRecorderToEndpointRecorder } from "../../Recorders/endpoints/getByUsername"; @@ -31,7 +31,7 @@ export const convertPageToEndpointPage = ({ folders, backgroundImage, slug, - tags, + attributes, thumbnail, createdAt, updatedAt, @@ -42,7 +42,7 @@ export const convertPageToEndpointPage = ({ ...(isValidPayloadImage(backgroundImage) ? { backgroundImage: convertImageToEndpointImage(backgroundImage) } : {}), - tagGroups: convertTagsEndpointTagsGroups(tags), + attributes: convertAttributesToEndpointAttributes(attributes), translations: translations.map( ({ content, language, sourceLanguage, title, pretitle, subtitle, summary, credits }) => ({ language: isPayloadType(language) ? language.id : language, diff --git a/src/collections/Tags/Tags.ts b/src/collections/Tags/Tags.ts index a26b23a..f4ef886 100644 --- a/src/collections/Tags/Tags.ts +++ b/src/collections/Tags/Tags.ts @@ -1,36 +1,16 @@ -import payload from "payload"; -import { CollectionBeforeChangeHook, CollectionConfig } from "payload/types"; +import { CollectionConfig } from "payload/types"; import { CollectionGroups, Collections } from "../../constants"; +import { rowField } from "../../fields/rowField/rowField"; import { slugField } from "../../fields/slugField/slugField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; import { buildCollectionConfig } from "../../utils/collectionConfig"; -const beforeChangeUpdateName: CollectionBeforeChangeHook = async ({ data }) => { - let name = data.slug; - - const parentId = data[fields.group]; - - if (parentId) { - const parent = await payload.findByID({ - collection: Collections.TagsGroups, - id: data[fields.group], - }); - name = `${parent.slug} / ${data.slug}`; - } - - return { - ...data, - [fields.name]: name, - }; -}; - const fields = { slug: "slug", - name: "name", translations: "translations", translationsName: "name", - group: "group", + page: "page", }; export const Tags: CollectionConfig = buildCollectionConfig({ @@ -38,16 +18,27 @@ export const Tags: CollectionConfig = buildCollectionConfig({ labels: { singular: "Tag", plural: "Tags" }, admin: { group: CollectionGroups.Meta, - useAsTitle: fields.name, - defaultColumns: [fields.slug, fields.group, fields.translations], + useAsTitle: fields.slug, + defaultColumns: [fields.slug, fields.translations], hooks: { beforeDuplicate: beforeDuplicateAddCopyTo(fields.slug), }, }, - hooks: { beforeChange: [beforeChangeUpdateName] }, fields: [ - { name: fields.name, type: "text", admin: { readOnly: true, hidden: true } }, - slugField({ name: fields.slug }), + rowField([ + slugField({ name: fields.slug }), + { + name: fields.page, + type: "relationship", + relationTo: Collections.Pages, + admin: { + description: + "You can declare a 'definition' page where more information of the tag will be presented.\ + The selected page will then feature a new section\ + where elements tagged with this tag will be listed.", + }, + }, + ]), translatedFields({ name: fields.translations, admin: { useAsTitle: fields.translationsName }, @@ -55,11 +46,5 @@ export const Tags: CollectionConfig = buildCollectionConfig({ minRows: 1, fields: [{ name: fields.translationsName, type: "text", required: true }], }), - { - name: fields.group, - type: "relationship", - required: true, - relationTo: Collections.TagsGroups, - }, ], }); diff --git a/src/collections/TagsGroups/TagsGroups.ts b/src/collections/TagsGroups/TagsGroups.ts deleted file mode 100644 index 11cea9f..0000000 --- a/src/collections/TagsGroups/TagsGroups.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { CollectionConfig } from "payload/types"; -import { CollectionGroups, Collections } from "../../constants"; -import { iconField } from "../../fields/iconField/iconField"; -import { slugField } from "../../fields/slugField/slugField"; -import { translatedFields } from "../../fields/translatedFields/translatedFields"; -import { buildCollectionConfig } from "../../utils/collectionConfig"; - -const fields = { - slug: "slug", - translations: "translations", - translationsName: "name", - icon: "icon", -}; - -export const TagsGroups: CollectionConfig = buildCollectionConfig({ - slug: Collections.TagsGroups, - labels: { singular: "Tags Group", plural: "Tags Groups" }, - admin: { - group: CollectionGroups.Meta, - useAsTitle: fields.slug, - defaultColumns: [fields.slug, fields.translations], - }, - fields: [ - slugField({ name: fields.slug }), - iconField({ name: fields.icon }), - translatedFields({ - name: fields.translations, - admin: { useAsTitle: fields.translationsName }, - required: true, - minRows: 1, - fields: [{ name: fields.translationsName, type: "text", required: true }], - }), - ], -}); diff --git a/src/collections/Videos/Videos.ts b/src/collections/Videos/Videos.ts index e24574f..ff47178 100644 --- a/src/collections/Videos/Videos.ts +++ b/src/collections/Videos/Videos.ts @@ -1,4 +1,5 @@ import { CollectionGroups, Collections } from "../../constants"; +import { attributesField } from "../../fields/attributesField/attributesField"; import { componentField } from "../../fields/componentField/componentField"; import { creditsField } from "../../fields/creditsField/creditsField"; import { imageField } from "../../fields/imageField/imageField"; @@ -23,6 +24,7 @@ const fields = { thumbnail: "thumbnail", duration: "duration", tags: "tags", + attributes: "attributes", platform: "platform", platformChannel: "channel", platformViews: "views", @@ -87,6 +89,7 @@ export const Videos = buildCollectionConfig({ ], }), tagsField({ name: fields.tags }), + attributesField({ name: fields.attributes }), creditsField({ name: fields.credits }), componentField({ name: fields.platform, diff --git a/src/collections/Videos/endpoints/getByID.ts b/src/collections/Videos/endpoints/getByID.ts index 6ff039e..b4a20e2 100644 --- a/src/collections/Videos/endpoints/getByID.ts +++ b/src/collections/Videos/endpoints/getByID.ts @@ -13,9 +13,9 @@ import { isValidPayloadMedia, } from "../../../utils/asserts"; import { + convertAttributesToEndpointAttributes, convertCreditsToEndpointCredits, convertRTCToEndpointRTC, - convertTagsEndpointTagsGroups, getLanguageId, } from "../../../utils/endpoints"; @@ -56,7 +56,7 @@ export const getByID: CollectionEndpoint = { export const convertVideoToEndpointVideo = ({ url, - tags, + attributes, translations, mimeType, createdAt, @@ -71,7 +71,7 @@ export const convertVideoToEndpointVideo = ({ credits, }: Video & PayloadMedia): EndpointVideo => ({ url, - tagGroups: convertTagsEndpointTagsGroups(tags), + attributes: convertAttributesToEndpointAttributes(attributes), createdAt, filename, filesize, diff --git a/src/constants.ts b/src/constants.ts index dee1ff9..7d13235 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -4,26 +4,26 @@ import type { BreakBlock, SectionBlock, TranscriptBlock } from "./types/collecti // END MOCKING SECTION export enum Collections { + Attributes = "attributes", Audios = "audios", ChronologyEvents = "chronology-events", + Collectibles = "collectibles", + CreditsRole = "credits-roles", Currencies = "currencies", + Folders = "folders", + GenericContents = "generic-contents", + Images = "images", Languages = "languages", + MediaThumbnails = "media-thumbnails", Pages = "pages", Recorders = "recorders", - Folders = "folders", - Tags = "tags", - TagsGroups = "tags-groups", - Images = "images", - Wordings = "wordings", - Collectibles = "collectibles", - GenericContents = "generic-contents", - WebsiteConfig = "website-config", - Videos = "videos", - VideosSubtitles = "videos-subtitles", - VideosChannels = "videos-channels", - MediaThumbnails = "media-thumbnails", Scans = "scans", - CreditsRole = "credits-roles", + Tags = "tags", + Videos = "videos", + VideosChannels = "videos-channels", + VideosSubtitles = "videos-subtitles", + Wordings = "wordings", + WebsiteConfig = "website-config", } export enum CollectionGroups { @@ -80,6 +80,12 @@ export enum CollectionStatus { Published = "published", } +export enum AttributeTypes { + Number = "Number", + Text = "Text", + Tags = "Tags", +} + /* RICH TEXT */ export type RichTextContent = { diff --git a/src/fields/attributesField/attributesField.ts b/src/fields/attributesField/attributesField.ts new file mode 100644 index 0000000..a1f19dc --- /dev/null +++ b/src/fields/attributesField/attributesField.ts @@ -0,0 +1,12 @@ +import { BlockField } from "payload/dist/fields/config/types"; +import { numberBlock } from "../../blocks/attributeBlocks/numberBlock"; +import { tagsBlock } from "../../blocks/attributeBlocks/tagsBlock"; +import { textBlock } from "../../blocks/attributeBlocks/textBlock"; + +type AttributesFieldProps = Omit; + +export const attributesField = ({ ...props }: AttributesFieldProps): BlockField => ({ + ...props, + type: "blocks", + blocks: [tagsBlock, numberBlock, textBlock], +}); diff --git a/src/payload.config.ts b/src/payload.config.ts index 67a9b3f..5001687 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -4,6 +4,7 @@ import { cloudStorage } from "@payloadcms/plugin-cloud-storage"; import path from "path"; import { buildConfig } from "payload/config"; import { sftpAdapter } from "payloadcms-sftp-storage"; +import { Attributes } from "./collections/Attributes/Attributes"; import { Audios } from "./collections/Audios/Audios"; import { ChronologyEvents } from "./collections/ChronologyEvents/ChronologyEvents"; import { Collectibles } from "./collections/Collectibles/Collectibles"; @@ -13,15 +14,14 @@ import { Folders } from "./collections/Folders/Folders"; import { GenericContents } from "./collections/GenericContents/GenericContents"; import { Images } from "./collections/Images/Images"; import { Languages } from "./collections/Languages/Languages"; +import { MediaThumbnails } from "./collections/MediaThumbnails/MediaThumbnails"; import { Pages } from "./collections/Pages/Pages"; import { Recorders } from "./collections/Recorders/Recorders"; import { Scans } from "./collections/Scans/Scans"; import { Tags } from "./collections/Tags/Tags"; -import { TagsGroups } from "./collections/TagsGroups/TagsGroups"; import { Videos } from "./collections/Videos/Videos"; import { VideosChannels } from "./collections/VideosChannels/VideosChannels"; import { VideosSubtitles } from "./collections/VideosSubtitles/VideosSubtitles"; -import { MediaThumbnails } from "./collections/VideosThumbnails/VideosThumbnails"; import { WebsiteConfig } from "./collections/WebsiteConfig/WebsiteConfig"; import { Wordings } from "./collections/Wordings/Wordings"; import { Icon } from "./components/Icon"; @@ -68,7 +68,7 @@ export default buildConfig({ Scans, Tags, - TagsGroups, + Attributes, CreditsRoles, Recorders, Languages, diff --git a/src/sdk.ts b/src/sdk.ts index b506b41..2a6b9a2 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -1,4 +1,5 @@ import { + AttributeTypes, CollectibleBindingTypes, CollectibleNature, CollectiblePageOrders, @@ -173,22 +174,42 @@ export type EndpointWording = { export type EndpointTag = { slug: string; + page?: EndpointPage; translations: { language: string; name: string; }[]; }; -export type EndpointTagsGroup = { +export type EndpointGenericAttribute = { slug: string; icon: string; translations: { language: string; name: string; }[]; - tags: EndpointTag[]; }; +export type EndpointNumberAttribute = EndpointGenericAttribute & { + type: AttributeTypes.Number; + value: number; +}; + +export type EndpointTextAttribute = EndpointGenericAttribute & { + type: AttributeTypes.Text; + value: string; +}; + +export type EndpointTagsAttribute = EndpointGenericAttribute & { + type: AttributeTypes.Tags; + value: EndpointTag[]; +}; + +export type EndpointAttribute = + | EndpointNumberAttribute + | EndpointTextAttribute + | EndpointTagsAttribute; + export type EndpointRole = { icon: string; translations: { @@ -205,7 +226,7 @@ export type EndpointCredit = { export type EndpointPage = { slug: string; thumbnail?: EndpointImage; - tagGroups: EndpointTagsGroup[]; + attributes: EndpointAttribute[]; backgroundImage?: EndpointImage; translations: { language: string; @@ -234,7 +255,7 @@ export type EndpointCollectible = { subtitle?: string; description?: RichTextContent; }[]; - tagGroups: EndpointTagsGroup[]; + attributes: EndpointAttribute[]; releaseDate?: string; languages: string[]; backgroundImage?: EndpointImage; @@ -458,7 +479,7 @@ export type EndpointMedia = { filesize: number; updatedAt: string; createdAt: string; - tagGroups: EndpointTagsGroup[]; + attributes: EndpointAttribute[]; translations: { language: string; pretitle?: string; diff --git a/src/styles.scss b/src/styles.scss index 3041394..8d0f4d9 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -75,6 +75,9 @@ html[data-theme="light"] { } .blocks-field__block-pill-cueBlock + .section-title, +.blocks-field__block-pill-tagsBlock + .section-title, +.blocks-field__block-pill-textBlock + .section-title, +.blocks-field__block-pill-numberBlock + .section-title, .blocks-field__block-pill-pageRange + .section-title, .blocks-field__block-pill-timeRange + .section-title, .blocks-field__block-pill-urlBlock + .section-title, diff --git a/src/types/collections.ts b/src/types/collections.ts index d5dfd8a..df37e48 100644 --- a/src/types/collections.ts +++ b/src/types/collections.ts @@ -32,7 +32,7 @@ export interface Config { "videos-channels": VideosChannel; scans: Scan; tags: Tag; - "tags-groups": TagsGroup; + attributes: Attribute; "credits-roles": CreditsRole; recorders: Recorder; languages: Language; @@ -56,6 +56,7 @@ export interface Page { thumbnail?: string | Image | null; backgroundImage?: string | Image | null; tags?: (string | Tag)[] | null; + attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null; translations: { language: string | Language; sourceLanguage: string | Language; @@ -133,6 +134,7 @@ export interface Image { }[] | null; tags?: (string | Tag)[] | null; + attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null; credits?: Credits; updatedAt: string; createdAt: string; @@ -175,25 +177,36 @@ export interface Language { */ export interface Tag { id: string; - name?: string | null; slug: string; + page?: (string | null) | Page; translations: { language: string | Language; name: string; id?: string | null; }[]; - group: string | TagsGroup; updatedAt: string; createdAt: string; } /** * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "tags-groups". + * via the `definition` "TagsBlock". */ -export interface TagsGroup { +export interface TagsBlock { + name: string | Attribute; + tags: (string | Tag)[]; + id?: string | null; + blockName?: string | null; + blockType: "tagsBlock"; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "attributes". + */ +export interface Attribute { id: string; slug: string; icon?: string | null; + type: "Number" | "Text" | "Tags"; translations: { language: string | Language; name: string; @@ -202,6 +215,28 @@ export interface TagsGroup { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "NumberBlock". + */ +export interface NumberBlock { + name: string | Attribute; + number: number; + id?: string | null; + blockName?: string | null; + blockType: "numberBlock"; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "TextBlock". + */ +export interface TextBlock { + name: string | Attribute; + text: string; + id?: string | null; + blockName?: string | null; + blockType: "textBlock"; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "credits-roles". @@ -318,6 +353,7 @@ export interface Collectible { nature: "Physical" | "Digital"; languages?: (string | Language)[] | null; tags?: (string | Tag)[] | null; + attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null; translations: { language: string | Language; pretitle?: string | null; @@ -582,6 +618,7 @@ export interface Audio { id?: string | null; }[]; tags?: (string | Tag)[] | null; + attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null; credits?: Credits; updatedAt: string; createdAt: string; @@ -657,6 +694,7 @@ export interface Video { id?: string | null; }[]; tags?: (string | Tag)[] | null; + attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null; credits?: Credits; platformEnabled?: boolean | null; platform?: { diff --git a/src/utils/endpoints.ts b/src/utils/endpoints.ts index d6ce0b7..8b6aeaa 100644 --- a/src/utils/endpoints.ts +++ b/src/utils/endpoints.ts @@ -2,9 +2,11 @@ import { convertAudioToEndpointAudio } from "../collections/Audios/endpoints/get import { convertCollectibleToEndpointCollectible } from "../collections/Collectibles/endpoints/getBySlugEndpoint"; import { convertFolderToEndpointFolder } from "../collections/Folders/endpoints/getBySlugEndpoint"; import { convertImageToEndpointImage } from "../collections/Images/endpoints/getByID"; +import { convertPageToEndpointPage } from "../collections/Pages/endpoints/getBySlugEndpoint"; import { convertRecorderToEndpointRecorder } from "../collections/Recorders/endpoints/getByUsername"; import { convertVideoToEndpointVideo } from "../collections/Videos/endpoints/getByID"; import { + AttributeTypes, RichTextBreakBlock, RichTextContent, RichTextSectionBlock, @@ -18,11 +20,11 @@ import { isUploadNodeVideoNode, } from "../constants"; import { + EndpointAttribute, EndpointCredit, EndpointRole, EndpointSource, EndpointTag, - EndpointTagsGroup, } from "../sdk"; import { Audio, @@ -32,10 +34,15 @@ import { Folder, Image, Language, + NumberBlock, Tag, + TagsBlock, + TextBlock, Video, } from "../types/collections"; import { + isDefined, + isEmpty, isPayloadArrayType, isPayloadType, isPublished, @@ -43,45 +50,14 @@ import { isValidPayloadMedia, } from "./asserts"; -export const convertTagsEndpointTagsGroups = ( - tags: (string | Tag)[] | null | undefined -): EndpointTagsGroup[] => { - if (!isPayloadArrayType(tags)) { - return []; - } - - const groups: EndpointTagsGroup[] = []; - - tags.forEach(({ translations, slug, group }) => { - if (isPayloadType(group)) { - const existingGroup = groups.find((existingGroup) => existingGroup.slug === group.slug); - - const endpointTag: EndpointTag = { - slug, - translations: translations.map(({ language, name }) => ({ - language: isPayloadType(language) ? language.id : language, - name, - })), - }; - - if (existingGroup) { - existingGroup.tags.push(endpointTag); - } else { - groups.push({ - slug: group.slug, - icon: group.icon ?? "material-symbols:category-outline", - tags: [endpointTag], - translations: group.translations.map(({ language, name }) => ({ - language: isPayloadType(language) ? language.id : language, - name, - })), - }); - } - } - }); - - return groups; -}; +const convertTagToEndpointTag = ({ slug, page, translations }: Tag): EndpointTag => ({ + slug, + ...(page && isPayloadType(page) ? { page: convertPageToEndpointPage(page) } : {}), + translations: translations.map(({ language, name }) => ({ + language: isPayloadType(language) ? language.id : language, + name, + })), +}); export const convertRTCToEndpointRTC = ( { root: { children, ...others } }: RichTextContent, @@ -233,3 +209,66 @@ export const convertCreditsToEndpointCredits = (credits?: Credits | null): Endpo }, ]; }) ?? []; + +export const convertAttributesToEndpointAttributes = ( + attributes: (TagsBlock | NumberBlock | TextBlock)[] | null | undefined +): EndpointAttribute[] => + attributes?.map(convertAttributeToEndpointAttribute).filter(isDefined) ?? []; + +const convertAttributeToEndpointAttribute = ( + attribute: TagsBlock | NumberBlock | TextBlock +): EndpointAttribute | undefined => { + switch (attribute.blockType) { + case "numberBlock": { + const { name, number } = attribute; + if (!isPayloadType(name)) return; + const { slug, icon, translations } = name; + return { + slug, + icon: icon ?? "material-symbols:category-outline", + translations: translations.map(({ language, name }) => ({ + language: isPayloadType(language) ? language.id : language, + name, + })), + type: AttributeTypes.Number, + value: number, + }; + } + + case "textBlock": { + const { name, text } = attribute; + if (!isPayloadType(name)) return; + if (isEmpty(text)) return; + const { slug, icon, translations } = name; + return { + slug, + icon: icon ?? "material-symbols:category-outline", + translations: translations.map(({ language, name }) => ({ + language: isPayloadType(language) ? language.id : language, + name, + })), + type: AttributeTypes.Text, + value: text, + }; + } + + case "tagsBlock": { + const { name, tags } = attribute; + if (!isPayloadType(name)) return; + if (!isPayloadArrayType(tags)) return; + if (tags.length === 0) return; + const { slug, icon, translations } = name; + + return { + slug, + icon: icon ?? "material-symbols:category-outline", + translations: translations.map(({ language, name }) => ({ + language: isPayloadType(language) ? language.id : language, + name, + })), + type: AttributeTypes.Tags, + value: tags.map(convertTagToEndpointTag), + }; + } + } +};