From 995ee9d311b4407f76a6692a416c1d0bb99690b7 Mon Sep 17 00:00:00 2001 From: Emmanuel Gautier Date: Tue, 13 Feb 2024 22:57:51 +0100 Subject: [PATCH] feat(@galactiks/explorer): add product and offer types --- .../contentlayer/content/articles/.gitkeep | 0 .../content/organizations/brand.mdx | 7 + .../contentlayer/content/products/product.mdx | 10 + examples/contentlayer/contentlayer.config.js | 2 + packages/config/src/config.ts | 15 +- packages/contentlayer/src/consts.ts | 5 +- .../src/document-types/product.ts | 8 + packages/contentlayer/src/fields/core.ts | 63 +++++++ .../contentlayer/src/fields/creative-work.ts | 4 - packages/contentlayer/src/fields/offer.ts | 15 ++ packages/contentlayer/src/fields/product.ts | 38 ++++ packages/contentlayer/src/fields/things.ts | 2 + .../src/fields/webpage-element.ts | 4 +- packages/contentlayer/src/index.ts | 1 + packages/contentlayer/src/types.ts | 173 ++++++++++++++++-- .../src/content/hydrate/listing-pages.ts | 10 +- .../src/content/hydrate/missing-fields.ts | 10 +- packages/explorer/src/content/hydrate/urls.ts | 2 +- .../src/content/metadata/breadcrumb.ts | 2 +- .../src/content/repositories/generated.ts | 10 +- packages/explorer/src/content/selectors.ts | 5 +- packages/explorer/src/types/index.ts | 9 + 22 files changed, 353 insertions(+), 42 deletions(-) delete mode 100644 examples/contentlayer/content/articles/.gitkeep create mode 100644 examples/contentlayer/content/organizations/brand.mdx create mode 100644 examples/contentlayer/content/products/product.mdx create mode 100644 packages/contentlayer/src/document-types/product.ts create mode 100644 packages/contentlayer/src/fields/offer.ts create mode 100644 packages/contentlayer/src/fields/product.ts diff --git a/examples/contentlayer/content/articles/.gitkeep b/examples/contentlayer/content/articles/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/contentlayer/content/organizations/brand.mdx b/examples/contentlayer/content/organizations/brand.mdx new file mode 100644 index 00000000..08c08cfe --- /dev/null +++ b/examples/contentlayer/content/organizations/brand.mdx @@ -0,0 +1,7 @@ +--- +name: Brand name +description: Brand description +identifier: brand +--- + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. diff --git a/examples/contentlayer/content/products/product.mdx b/examples/contentlayer/content/products/product.mdx new file mode 100644 index 00000000..63e32339 --- /dev/null +++ b/examples/contentlayer/content/products/product.mdx @@ -0,0 +1,10 @@ +--- +name: Product +description: Product description +identifier: product +dateCreated: 1970-01-01 +brand: + '@id': brand +--- + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. diff --git a/examples/contentlayer/contentlayer.config.js b/examples/contentlayer/contentlayer.config.js index 19d59818..63da2d8d 100644 --- a/examples/contentlayer/contentlayer.config.js +++ b/examples/contentlayer/contentlayer.config.js @@ -4,6 +4,7 @@ import { PageDocumentType, PersonDocumentType, PlaceDocumentType, + ProductDocumentType, WebsiteDocumentType, WebpageElementDocumentType, } from '@galactiks/contentlayer'; @@ -17,6 +18,7 @@ const contentLayerConfig = makeSource({ PageDocumentType, PersonDocumentType, PlaceDocumentType, + ProductDocumentType, WebsiteDocumentType, WebpageElementDocumentType, ], diff --git a/packages/config/src/config.ts b/packages/config/src/config.ts index cbbd605b..1486d853 100644 --- a/packages/config/src/config.ts +++ b/packages/config/src/config.ts @@ -37,7 +37,8 @@ const galactiksConfigFileSchema = z.object({ organizations: pagesObjectItemSchema, pages: pagesObjectItemSchema, people: pagesObjectItemSchema, - place: pagesObjectItemSchema, + products: pagesObjectItemSchema, + places: pagesObjectItemSchema, tags: pagesObjectItemSchema, }) .optional(), @@ -65,6 +66,10 @@ const defaultPages: GalactiksConfig['pages'] = { path: '/{+isPartOf}/{/identifier}/', }, + organizations: { + path: '/organizations/{/identifier}/', + }, + pages: { path: '/{+isPartOf}{/identifier}/', }, @@ -73,8 +78,12 @@ const defaultPages: GalactiksConfig['pages'] = { path: '/authors/{/identifier}/', }, - organizations: { - path: '/organizations/{/identifier}/', + products: { + path: '/products/{+isPartOf}/{/identifier}/', + }, + + places: { + path: '/places/{+isPartOf}/{/identifier}/', }, tags: { diff --git a/packages/contentlayer/src/consts.ts b/packages/contentlayer/src/consts.ts index c26e1196..c9902aed 100644 --- a/packages/contentlayer/src/consts.ts +++ b/packages/contentlayer/src/consts.ts @@ -5,16 +5,19 @@ export enum pageDocumentTypes { Organization = 'organizations', Page = 'pages', Person = 'people', - Place = 'place', + Place = 'places', + Product = 'products', Tag = 'tags', } export enum documentTypes { Article = pageDocumentTypes.Article, + Offer = 'offer', Organization = pageDocumentTypes.Organization, Page = pageDocumentTypes.Page, Person = pageDocumentTypes.Person, Place = pageDocumentTypes.Place, + Product = pageDocumentTypes.Product, Tag = pageDocumentTypes.Tag, WebPageElement = 'webPageElements', Website = 'websites', diff --git a/packages/contentlayer/src/document-types/product.ts b/packages/contentlayer/src/document-types/product.ts new file mode 100644 index 00000000..86e81a05 --- /dev/null +++ b/packages/contentlayer/src/document-types/product.ts @@ -0,0 +1,8 @@ +import { defineDocumentType } from 'contentlayer/source-files'; +import { ContentLayerProductFields } from '../fields/product.js'; + +export const ProductDocumentType = defineDocumentType(() => ({ + ...ContentLayerProductFields, + filePathPattern: 'products/**/*.md?(x)', + contentType: 'mdx', +})); diff --git a/packages/contentlayer/src/fields/core.ts b/packages/contentlayer/src/fields/core.ts index 1b963d32..a62b7e85 100644 --- a/packages/contentlayer/src/fields/core.ts +++ b/packages/contentlayer/src/fields/core.ts @@ -18,6 +18,20 @@ export const idDocumentType = defineNestedType(() => ({ fields: idFields, })); +/* + * Those fields are following schema.org fields specifications: + * - https://schema.org/inLanguage + * - https://schema.org/translationOfWork + * - https://schema.org/workTranslation + * + * Those fields are applied for every document types when a readable text exists (ex: description, name, ...etc). + */ +export const translationFields: FieldDefs = { + inLanguage: { type: 'string', required: false }, + translationOfWork: { type: 'nested', of: idDocumentType, required: false }, + workTranslation: { type: 'nested', of: idDocumentType, required: false }, +}; + export const itemListElementFields: NestedType = defineNestedType(() => ({ name: 'ItemListElement', fields: { @@ -49,3 +63,52 @@ export const postalAddress = defineNestedType(() => ({ streetAddress: { type: 'string', required: false }, }, })); + +export const quantitativeValue = defineNestedType(() => ({ + name: 'QuantitativeValue', + fields: { + maxValue: { type: 'number', required: false }, + minValue: { type: 'number', required: false }, + value: { type: 'number', required: true }, + }, +})); + +const EUEnergyEfficiencyEnumeration = [ + 'EUEnergyEfficiencyCategoryA', + 'EUEnergyEfficiencyCategoryA1Plus', + 'EUEnergyEfficiencyCategoryA2Plus', + 'EUEnergyEfficiencyCategoryA3Plus', + 'EUEnergyEfficiencyCategoryB', + 'EUEnergyEfficiencyCategoryC', + 'EUEnergyEfficiencyCategoryD', + 'EUEnergyEfficiencyCategoryE', + 'EUEnergyEfficiencyCategoryF', + 'EUEnergyEfficiencyCategoryG', +]; +export const energyConsumptionDetails = defineNestedType(() => ({ + name: 'EnergyConsumptionDetails', + fields: { + energyEfficiencyScaleMax: { + type: 'enum', + options: EUEnergyEfficiencyEnumeration, + required: false, + }, + energyEfficiencyScaleMin: { + type: 'enum', + options: EUEnergyEfficiencyEnumeration, + required: false, + }, + }, +})); + +export const propertyValue = defineNestedType(() => ({ + name: 'PropertyValue', + fields: { + name: { type: 'string', required: true }, + value: { type: 'string', required: true }, + minValue: { type: 'number', required: false }, + maxValue: { type: 'number', required: false }, + unitCode: { type: 'string', required: false }, + unitText: { type: 'string', required: false }, + }, +})); diff --git a/packages/contentlayer/src/fields/creative-work.ts b/packages/contentlayer/src/fields/creative-work.ts index 618c7f8d..a4cfce94 100644 --- a/packages/contentlayer/src/fields/creative-work.ts +++ b/packages/contentlayer/src/fields/creative-work.ts @@ -1,5 +1,4 @@ import type { FieldDefs } from 'contentlayer/source-files'; -import { idDocumentType } from './core.js'; import { thingsFields } from './things.js'; export const creativeWorkFields: FieldDefs = { @@ -10,8 +9,5 @@ export const creativeWorkFields: FieldDefs = { dateModified: { type: 'date', required: false }, datePublished: { type: 'date', required: false }, isPartOf: { type: 'string', required: false }, - inLanguage: { type: 'string', required: false }, - translationOfWork: { type: 'nested', of: idDocumentType, required: false }, - workTranslation: { type: 'nested', of: idDocumentType, required: false }, keywords: { type: 'list', required: false, of: { type: 'string' } }, }; diff --git a/packages/contentlayer/src/fields/offer.ts b/packages/contentlayer/src/fields/offer.ts new file mode 100644 index 00000000..fe72db09 --- /dev/null +++ b/packages/contentlayer/src/fields/offer.ts @@ -0,0 +1,15 @@ +import type { DocumentTypeDef } from '../consts.js'; +import { galactiksFields, idDocumentType, propertyValue } from './core.js'; +import { thingsFields } from './things.js'; + +export const ContentLayerOfferFields: DocumentTypeDef = { + name: 'Offer', + fields: { + ...galactiksFields, + ...thingsFields, + additionalProperty: { type: 'nested', of: propertyValue }, + price: { type: 'number', required: true }, + priceCurrency: { type: 'string', required: true }, + seller: { type: 'nested', of: idDocumentType, required: false }, + }, +}; diff --git a/packages/contentlayer/src/fields/product.ts b/packages/contentlayer/src/fields/product.ts new file mode 100644 index 00000000..4f1dc320 --- /dev/null +++ b/packages/contentlayer/src/fields/product.ts @@ -0,0 +1,38 @@ +import type { DocumentTypeDef } from '../consts.js'; +import { + energyConsumptionDetails, + galactiksFields, + idDocumentType, + quantitativeValue, +} from './core.js'; +import { thingsFields } from './things.js'; +import { ContentLayerOfferFields } from './offer.js'; + +export const ContentLayerProductFields: DocumentTypeDef = { + name: 'Product', + fields: { + ...galactiksFields, + ...thingsFields, + brand: { type: 'nested', of: idDocumentType, required: false }, + color: { type: 'string', required: false }, + depth: { type: 'nested', of: quantitativeValue, required: false }, + gtin: { type: 'string', required: false }, + hasEnergyConsumptionDetails: { + type: 'nested', + of: energyConsumptionDetails, + required: false, + }, + hasMeasurement: { type: 'nested', of: quantitativeValue, required: false }, + height: { type: 'nested', of: quantitativeValue, required: false }, + keywords: { type: 'list', required: false, of: { type: 'string' } }, + manufacturer: { type: 'nested', of: idDocumentType, required: false }, + model: { type: 'nested', of: idDocumentType, required: false }, + offers: { + type: 'list', + of: { type: 'nested', def: () => ContentLayerOfferFields }, + }, + sku: { type: 'string', required: false }, + weight: { type: 'nested', of: quantitativeValue, required: false }, + width: { type: 'nested', of: quantitativeValue, required: false }, + }, +}; diff --git a/packages/contentlayer/src/fields/things.ts b/packages/contentlayer/src/fields/things.ts index 0ff353bb..6fec9dce 100644 --- a/packages/contentlayer/src/fields/things.ts +++ b/packages/contentlayer/src/fields/things.ts @@ -1,6 +1,8 @@ import type { FieldDefs } from 'contentlayer/source-files'; +import { translationFields } from './core.js'; export const thingsFields: FieldDefs = { + ...translationFields, name: { type: 'string', required: true }, description: { type: 'string', required: true }, url: { type: 'string', required: false }, diff --git a/packages/contentlayer/src/fields/webpage-element.ts b/packages/contentlayer/src/fields/webpage-element.ts index 3c7b540c..431c34b8 100644 --- a/packages/contentlayer/src/fields/webpage-element.ts +++ b/packages/contentlayer/src/fields/webpage-element.ts @@ -1,5 +1,5 @@ import type { DocumentTypeDef } from '../consts.js'; -import { itemListFields } from './core.js'; +import { itemListFields, translationFields } from './core.js'; export const ContentLayerWebPageElementFields: DocumentTypeDef = { name: 'WebPageElement', @@ -9,7 +9,7 @@ export const ContentLayerWebPageElementFields: DocumentTypeDef = { options: ['SiteNavigationElement', 'WPFooter', 'WPHeader', 'WPSideBar'], required: false, }, // avoid collision with contentlayer type - inLanguage: { type: 'string', required: false }, + ...translationFields, // ...itemListFields, }, }; diff --git a/packages/contentlayer/src/index.ts b/packages/contentlayer/src/index.ts index 81d21971..4e5c7d30 100644 --- a/packages/contentlayer/src/index.ts +++ b/packages/contentlayer/src/index.ts @@ -3,6 +3,7 @@ export { OrganizationDocumentType } from './document-types/organization.js'; export { PageDocumentType } from './document-types/page.js'; export { PersonDocumentType } from './document-types/person.js'; export { PlaceDocumentType } from './document-types/place.js'; +export { ProductDocumentType } from './document-types/product.js'; export { WebpageElementDocumentType } from './document-types/webpage-element.js'; export { WebsiteDocumentType } from './document-types/website.js'; diff --git a/packages/contentlayer/src/types.ts b/packages/contentlayer/src/types.ts index 7201800e..a9f36417 100644 --- a/packages/contentlayer/src/types.ts +++ b/packages/contentlayer/src/types.ts @@ -19,6 +19,9 @@ export type Article = { listingPage: boolean; slug?: string | undefined; path?: string | undefined; + inLanguage?: string | undefined; + translationOfWork?: Id | undefined; + workTranslation?: Id | undefined; name: string; description: string; url?: string | undefined; @@ -31,9 +34,6 @@ export type Article = { dateModified?: IsoDateTimeString | undefined; datePublished?: IsoDateTimeString | undefined; isPartOf?: string | undefined; - inLanguage?: string | undefined; - translationOfWork?: Id | undefined; - workTranslation?: Id | undefined; keywords?: string[] | undefined; /** MDX file body */ body: MDX; @@ -47,6 +47,9 @@ export type Organization = { listingPage: boolean; slug?: string | undefined; path?: string | undefined; + inLanguage?: string | undefined; + translationOfWork?: Id | undefined; + workTranslation?: Id | undefined; name: string; description: string; url?: string | undefined; @@ -72,6 +75,9 @@ export type Page = { listingPage: boolean; slug?: string | undefined; path?: string | undefined; + inLanguage?: string | undefined; + translationOfWork?: Id | undefined; + workTranslation?: Id | undefined; name: string; description: string; url?: string | undefined; @@ -84,9 +90,6 @@ export type Page = { dateModified?: IsoDateTimeString | undefined; datePublished?: IsoDateTimeString | undefined; isPartOf?: string | undefined; - inLanguage?: string | undefined; - translationOfWork?: Id | undefined; - workTranslation?: Id | undefined; keywords?: string[] | undefined; /** MDX file body */ body: MDX; @@ -100,6 +103,9 @@ export type Person = { listingPage: boolean; slug?: string | undefined; path?: string | undefined; + inLanguage?: string | undefined; + translationOfWork?: Id | undefined; + workTranslation?: Id | undefined; name: string; description: string; url?: string | undefined; @@ -112,9 +118,6 @@ export type Person = { dateModified?: IsoDateTimeString | undefined; datePublished?: IsoDateTimeString | undefined; isPartOf?: string | undefined; - inLanguage?: string | undefined; - translationOfWork?: Id | undefined; - workTranslation?: Id | undefined; keywords?: string[] | undefined; additionalName?: string | undefined; email?: string | undefined; @@ -134,6 +137,9 @@ export type Place = { listingPage: boolean; slug?: string | undefined; path?: string | undefined; + inLanguage?: string | undefined; + translationOfWork?: Id | undefined; + workTranslation?: Id | undefined; name: string; description: string; url?: string | undefined; @@ -146,9 +152,6 @@ export type Place = { dateModified?: IsoDateTimeString | undefined; datePublished?: IsoDateTimeString | undefined; isPartOf?: string | undefined; - inLanguage?: string | undefined; - translationOfWork?: Id | undefined; - workTranslation?: Id | undefined; keywords?: string[] | undefined; address?: PostalAddress | undefined; latitude?: string | undefined; @@ -158,6 +161,41 @@ export type Place = { body: MDX; }; +export type Product = { + /** File path relative to `contentDirPath` */ + _id: string; + _raw: Local.RawDocumentData; + type: 'Product'; + listingPage: boolean; + slug?: string | undefined; + path?: string | undefined; + inLanguage?: string | undefined; + translationOfWork?: Id | undefined; + workTranslation?: Id | undefined; + name: string; + description: string; + url?: string | undefined; + identifier: string; + image?: any | undefined; + sameAs?: string | undefined; + brand?: Id | undefined; + color?: string | undefined; + depth?: QuantitativeValue | undefined; + gtin?: string | undefined; + hasEnergyConsumptionDetails?: EnergyConsumptionDetails | undefined; + hasMeasurement?: QuantitativeValue | undefined; + height?: QuantitativeValue | undefined; + keywords?: string[] | undefined; + manufacturer?: Id | undefined; + model?: Id | undefined; + offers?: Offer[] | undefined; + sku?: string | undefined; + weight?: QuantitativeValue | undefined; + width?: QuantitativeValue | undefined; + /** MDX file body */ + body: MDX; +}; + export type WebPageElement = { /** File path relative to `contentDirPath` */ _id: string; @@ -170,6 +208,8 @@ export type WebPageElement = { | 'WPSideBar' | undefined; inLanguage?: string | undefined; + translationOfWork?: Id | undefined; + workTranslation?: Id | undefined; name?: string | undefined; identifier: string; itemListElement: ItemListElement[]; @@ -182,6 +222,9 @@ export type Website = { _id: string; _raw: Local.RawDocumentData; type: 'Website'; + inLanguage?: string | undefined; + translationOfWork?: Id | undefined; + workTranslation?: Id | undefined; name: string; description: string; url?: string | undefined; @@ -194,9 +237,6 @@ export type Website = { dateModified?: IsoDateTimeString | undefined; datePublished?: IsoDateTimeString | undefined; isPartOf?: string | undefined; - inLanguage?: string | undefined; - translationOfWork?: Id | undefined; - workTranslation?: Id | undefined; keywords?: string[] | undefined; issn?: string | undefined; /** MDX file body */ @@ -204,6 +244,37 @@ export type Website = { }; /** Nested types */ +export type EnergyConsumptionDetails = { + /** File path relative to `contentDirPath` */ + _id: string; + _raw: Local.RawDocumentData; + type: 'EnergyConsumptionDetails'; + energyEfficiencyScaleMax?: + | 'EUEnergyEfficiencyCategoryA' + | 'EUEnergyEfficiencyCategoryA1Plus' + | 'EUEnergyEfficiencyCategoryA2Plus' + | 'EUEnergyEfficiencyCategoryA3Plus' + | 'EUEnergyEfficiencyCategoryB' + | 'EUEnergyEfficiencyCategoryC' + | 'EUEnergyEfficiencyCategoryD' + | 'EUEnergyEfficiencyCategoryE' + | 'EUEnergyEfficiencyCategoryF' + | 'EUEnergyEfficiencyCategoryG' + | undefined; + energyEfficiencyScaleMin?: + | 'EUEnergyEfficiencyCategoryA' + | 'EUEnergyEfficiencyCategoryA1Plus' + | 'EUEnergyEfficiencyCategoryA2Plus' + | 'EUEnergyEfficiencyCategoryA3Plus' + | 'EUEnergyEfficiencyCategoryB' + | 'EUEnergyEfficiencyCategoryC' + | 'EUEnergyEfficiencyCategoryD' + | 'EUEnergyEfficiencyCategoryE' + | 'EUEnergyEfficiencyCategoryF' + | 'EUEnergyEfficiencyCategoryG' + | undefined; +}; + export type Id = { type: 'Id'; '@id': string; @@ -220,6 +291,29 @@ export type ItemListElement = { itemListElement?: ItemListElement[] | undefined; }; +export type Offer = { + /** File path relative to `contentDirPath` */ + _id: string; + _raw: Local.RawDocumentData; + type: 'Offer'; + listingPage: boolean; + slug?: string | undefined; + path?: string | undefined; + inLanguage?: string | undefined; + translationOfWork?: Id | undefined; + workTranslation?: Id | undefined; + name: string; + description: string; + url?: string | undefined; + identifier: string; + image?: any | undefined; + sameAs?: string | undefined; + additionalProperty?: PropertyValue | undefined; + price: number; + priceCurrency: string; + seller?: Id | undefined; +}; + export type PostalAddress = { /** File path relative to `contentDirPath` */ _id: string; @@ -233,6 +327,29 @@ export type PostalAddress = { streetAddress?: string | undefined; }; +export type PropertyValue = { + /** File path relative to `contentDirPath` */ + _id: string; + _raw: Local.RawDocumentData; + type: 'PropertyValue'; + name: string; + value: string; + minValue?: number | undefined; + maxValue?: number | undefined; + unitCode?: string | undefined; + unitText?: string | undefined; +}; + +export type QuantitativeValue = { + /** File path relative to `contentDirPath` */ + _id: string; + _raw: Local.RawDocumentData; + type: 'QuantitativeValue'; + maxValue?: number | undefined; + minValue?: number | undefined; + value: number; +}; + /** Helper types */ export type AllTypes = DocumentTypes | NestedTypes; @@ -244,6 +361,7 @@ export type DocumentTypes = | Page | Person | Place + | Product | WebPageElement | Website; export type DocumentTypeNames = @@ -252,11 +370,26 @@ export type DocumentTypeNames = | 'Page' | 'Person' | 'Place' + | 'Product' | 'WebPageElement' | 'Website'; -export type NestedTypes = Id | ItemListElement | PostalAddress; -export type NestedTypeNames = 'Id' | 'ItemListElement' | 'PostalAddress'; +export type NestedTypes = + | EnergyConsumptionDetails + | Id + | ItemListElement + | Offer + | PostalAddress + | PropertyValue + | QuantitativeValue; +export type NestedTypeNames = + | 'EnergyConsumptionDetails' + | 'Id' + | 'ItemListElement' + | 'Offer' + | 'PostalAddress' + | 'PropertyValue' + | 'QuantitativeValue'; export type DataExports = { allDocuments: DocumentTypes[]; @@ -265,6 +398,7 @@ export type DataExports = { allPages: Page[]; allPeople: Person[]; allPlaces: Place[]; + allProducts: Product[]; allWebsites: Website[]; allWebPageElements: WebPageElement[]; }; @@ -290,12 +424,17 @@ export type DocumentTypeMap = { Page: Page; Person: Person; Place: Place; + Product: Product; WebPageElement: WebPageElement; Website: Website; }; export type NestedTypeMap = { + EnergyConsumptionDetails: EnergyConsumptionDetails; Id: Id; ItemListElement: ItemListElement; + Offer: Offer; PostalAddress: PostalAddress; + PropertyValue: PropertyValue; + QuantitativeValue: QuantitativeValue; }; diff --git a/packages/explorer/src/content/hydrate/listing-pages.ts b/packages/explorer/src/content/hydrate/listing-pages.ts index 2f34021a..bd941c37 100644 --- a/packages/explorer/src/content/hydrate/listing-pages.ts +++ b/packages/explorer/src/content/hydrate/listing-pages.ts @@ -28,13 +28,13 @@ export const computeRemainingListingPages = return documents.reduce((acc, _d) => { const templateDocument: Partial = { - dateCreated: _d.dateCreated, - datePublished: _d.datePublished, - dateModified: _d.dateModified, + dateCreated: 'dateCreated' in _d ? _d.dateCreated : undefined, + datePublished: 'datePublished' in _d ? _d.datePublished : undefined, + dateModified: 'dateModified' in _d ? _d.dateModified : undefined, inLanguage: _d.inLanguage, }; - if (_d.isPartOf) { + if ('isPartOf' in _d && _d.isPartOf) { const _isPartOfIdentifier = createIdentifierFromString(_d.isPartOf); // If parent page does not exist, create it @@ -50,7 +50,7 @@ export const computeRemainingListingPages = const translationOfWorkDocument = getDocumentByIdentifier( _d.translationOfWork['@id'] ); - if (translationOfWorkDocument?.isPartOf) { + if (translationOfWorkDocument && 'isPartOf' in translationOfWorkDocument && typeof translationOfWorkDocument.isPartOf === 'string') { translationOfWork = { type: 'Id', '@id': translationOfWorkDocument.isPartOf, diff --git a/packages/explorer/src/content/hydrate/missing-fields.ts b/packages/explorer/src/content/hydrate/missing-fields.ts index b7634505..2bda3b87 100644 --- a/packages/explorer/src/content/hydrate/missing-fields.ts +++ b/packages/explorer/src/content/hydrate/missing-fields.ts @@ -50,16 +50,18 @@ export const computeMissingFields = }; return documents.map((document) => { - const dateCreated = document.dateCreated ? new Date(document.dateCreated) : new Date(); + const dateCreated = 'dateCreated' in document && document.dateCreated + ? new Date(document.dateCreated) + : new Date(); const contentWithoutHeaders: Omit = { ...document, - author: getAuthor(document.author, document.inLanguage), + author: 'author' in document ? getAuthor(document.author, document.inLanguage) : undefined, breadcrumb: buildBreadcrumb(document), dateCreated, - dateModified: document.dateModified + dateModified: 'dateModified' in document && document.dateModified ? new Date(document.dateModified) : dateCreated, - datePublished: document.datePublished + datePublished: 'datePublished' in document && document.datePublished ? new Date(document.datePublished) : dateCreated, }; diff --git a/packages/explorer/src/content/hydrate/urls.ts b/packages/explorer/src/content/hydrate/urls.ts index 60d16883..1126cdef 100644 --- a/packages/explorer/src/content/hydrate/urls.ts +++ b/packages/explorer/src/content/hydrate/urls.ts @@ -96,7 +96,7 @@ export const computeDocumentsUrl = } let isPartOfPath: string | undefined; - if (pathTemplate.indexOf('isPartOf') && document.isPartOf) { + if ('isPartOf' in document && document.isPartOf && pathTemplate.indexOf('isPartOf')) { // The page has been created if missing const isPartOf = getDocumentByIdentifierAndLanguage( document.isPartOf, diff --git a/packages/explorer/src/content/metadata/breadcrumb.ts b/packages/explorer/src/content/metadata/breadcrumb.ts index 4b052c99..9ff92e47 100644 --- a/packages/explorer/src/content/metadata/breadcrumb.ts +++ b/packages/explorer/src/content/metadata/breadcrumb.ts @@ -53,7 +53,7 @@ export const breadcrumbBuilder = } return recursiveBreadcrumbExploration( - page.isPartOf || homeIdentifier, + ('isPartOf' in page && page.isPartOf) || homeIdentifier, inLanguage, itemList ); diff --git a/packages/explorer/src/content/repositories/generated.ts b/packages/explorer/src/content/repositories/generated.ts index c3fe6b53..5a834044 100644 --- a/packages/explorer/src/content/repositories/generated.ts +++ b/packages/explorer/src/content/repositories/generated.ts @@ -54,11 +54,17 @@ const getWebPageDocuments = async (): Promise => { const generated = await getGenerated(); const publishedDocuments = new Array() - .concat(generated.allPages || []) .concat(generated.allArticles || []) + .concat(generated.allOrganizations || []) + .concat(generated.allPages || []) .concat(generated.allPeople || []) .concat(generated.allPlaces || []) - .filter((_d) => !_d.datePublished || new Date(_d.datePublished) <= dateNow); + .concat(generated.allProducts || []) + .filter( + (_d) => + !('datePublished' in _d) || + (_d.datePublished && new Date(_d.datePublished) <= dateNow) + ); _documents = await computeDocuments({ config, websites: await getWebsites(), diff --git a/packages/explorer/src/content/selectors.ts b/packages/explorer/src/content/selectors.ts index 73b61b33..83ec93f3 100644 --- a/packages/explorer/src/content/selectors.ts +++ b/packages/explorer/src/content/selectors.ts @@ -1,6 +1,7 @@ import type { ContentlayerDocumentTypes, ContentlayerWebPageDocument, + CreativeWorkWebPageDocument, } from '../types/index.js'; import { MaxDepthLimitReachedError } from '../exceptions/max-depth-limit-reached.error.js'; import { pageDepthLimit } from './consts.js'; @@ -44,7 +45,7 @@ export const documentsByLanguageSelector = (inLanguage: string) => documents.filter((_d) => isInLanguage(_d, inLanguage)); export const documentsByAuthorSelector = - >(documents: T[]) => + >(documents: T[]) => (author: string) => documents.filter((_d) => _d.author === author); export const usedLanguagesInDocumentsSelector = @@ -59,7 +60,7 @@ export const usedLanguagesInDocumentsSelector = ); export const pageDepthSelector = - >( + >( documents: T[] ) => (document: T): number => { diff --git a/packages/explorer/src/types/index.ts b/packages/explorer/src/types/index.ts index 7b991b18..b0df98c5 100644 --- a/packages/explorer/src/types/index.ts +++ b/packages/explorer/src/types/index.ts @@ -1,9 +1,11 @@ import type { MDX, Article as ContentlayerArticle, + Organization as ContentlayerOrganization, Page as ContentlayerPage, Person as ContentlayerPerson, Place as ContentlayerPlace, + Product as ContentlayerProduct, } from '@galactiks/contentlayer'; import type { MetadataHeaders, Page } from './_schemas'; @@ -17,8 +19,10 @@ export type { DocumentTypes as ContentlayerDocumentTypes, DataExports as ContentlayerDataExports, Organization as ContentlayerOrganization, + Page as ContentlayerPage, Person as ContentlayerPerson, Place as ContentlayerPlace, + Product as ContentlayerProduct, Website as ContentlayerWebsite, WebPageElement as ContentlayerWebPageElement, } from '@galactiks/contentlayer'; @@ -28,10 +32,15 @@ export type * from './render'; type ContentlayerTagPage = Omit & { type: 'Tag' }; export type ContentlayerWebPageDocument = | ContentlayerArticle + | ContentlayerOrganization | ContentlayerPage | ContentlayerPerson | ContentlayerPlace + | ContentlayerProduct | ContentlayerTagPage; +export type CreativeWorkWebPageDocument = + | ContentlayerArticle + | ContentlayerPage export type ContentlayerWebPageDocumentWithRender = ContentlayerDocumentWithRender;