From c571ccfdbe86452390146b6baaeecda50c085d4f Mon Sep 17 00:00:00 2001 From: Emmanuel Gautier Date: Mon, 1 Jan 2024 12:29:24 +0100 Subject: [PATCH] feat(@galactiks/explorer): create multiple pages when locales with the same language exist --- packages/explorer/package.json | 2 + .../explorer/src/core/content/compute.mts | 226 ------------------ .../src/core/content/hydrate/common.mts | 27 +++ .../src/core/content/hydrate/index.mts | 30 +++ .../core/content/hydrate/listing-pages.mts | 82 +++++++ .../core/content/hydrate/missing-fields.mts | 68 ++++++ .../src/core/content/{ => hydrate}/render.mts | 12 +- .../core/content/hydrate/same-language.mts | 49 ++++ .../src/core/content/hydrate/types.mts | 11 + .../src/core/content/{ => hydrate}/urls.mts | 19 +- packages/explorer/src/core/content/index.mts | 2 +- .../src/core/content/metadata/alternates.mts | 3 +- .../src/core/content/metadata/breadcrumb.mts | 3 +- .../src/core/content/metadata/headers.mts | 2 +- .../src/core/content/metadata/ogp/article.mts | 2 +- .../src/core/content/metadata/ogp/og.mts | 2 +- .../src/core/content/metadata/ogp/website.mts | 2 +- .../core/content/metadata/schemas/article.mts | 2 +- .../content/metadata/schemas/breadcrumb.mts | 2 +- .../content/metadata/schemas/organization.mts | 2 +- .../core/content/metadata/schemas/person.mts | 2 +- .../metadata/schemas/structured-data.mts | 2 +- .../src/core/content/metadata/twitter.mts | 2 +- .../src/core/content/repositories/filters.mts | 2 +- .../core/content/repositories/generated.mts | 4 +- .../src/core/content/repositories/index.mts | 5 +- .../explorer/src/core/content/selectors.mts | 4 +- .../src/core/{content => }/types/_schemas.mts | 0 .../src/core/{content => }/types/index.mts | 16 +- packages/explorer/src/core/types/render.mts | 6 + packages/explorer/tsconfig.json | 7 +- pnpm-lock.yaml | 74 +++--- 32 files changed, 369 insertions(+), 303 deletions(-) delete mode 100644 packages/explorer/src/core/content/compute.mts create mode 100644 packages/explorer/src/core/content/hydrate/common.mts create mode 100644 packages/explorer/src/core/content/hydrate/index.mts create mode 100644 packages/explorer/src/core/content/hydrate/listing-pages.mts create mode 100644 packages/explorer/src/core/content/hydrate/missing-fields.mts rename packages/explorer/src/core/content/{ => hydrate}/render.mts (54%) create mode 100644 packages/explorer/src/core/content/hydrate/same-language.mts create mode 100644 packages/explorer/src/core/content/hydrate/types.mts rename packages/explorer/src/core/content/{ => hydrate}/urls.mts (90%) rename packages/explorer/src/core/{content => }/types/_schemas.mts (100%) rename packages/explorer/src/core/{content => }/types/index.mts (65%) create mode 100644 packages/explorer/src/core/types/render.mts diff --git a/packages/explorer/package.json b/packages/explorer/package.json index dab1ab9b..0077f725 100644 --- a/packages/explorer/package.json +++ b/packages/explorer/package.json @@ -39,6 +39,7 @@ "@std-uritemplate/std-uritemplate": "0.0.49", "debug": "4.3.4", "esbuild": "0.19.11", + "lodash.groupby": "^4.6.0", "mdx-bundler": "10.0.1", "react": "18.2.0", "slugify": "1.6.6", @@ -46,6 +47,7 @@ }, "devDependencies": { "@types/debug": "4.1.12", + "@types/lodash.groupby": "^4.6.9", "@types/react": "18.2.46", "schema-dts": "1.1.2" } diff --git a/packages/explorer/src/core/content/compute.mts b/packages/explorer/src/core/content/compute.mts deleted file mode 100644 index 95272745..00000000 --- a/packages/explorer/src/core/content/compute.mts +++ /dev/null @@ -1,226 +0,0 @@ -import type { GalactiksConfig } from '@galactiks/config'; -import type { Id } from '@galactiks/contentlayer'; - -import { - alternatesHeaderBuilder, - breadcrumbBuilder, - getBasicHeaders, - getOpenGraphObjects, - getStructuredDataSchemas, - getTwitterCard, -} from './metadata/index.mjs'; -import { - addBodyRender, - emptyRender, - type ContentlayerDocumentWithRender, - type ContentlayerWebPageDocumentWithRender, -} from './render.mjs'; -import { - computeDocumentsUrl, - type ContentlayerDocumentWithURL, -} from './urls.mjs'; -import { - documentByIdentifierSelector, - documentByTypeAndIdentifierAndLanguageSelector, - isInLanguage, -} from './selectors.mjs'; -import type { - Content, - ContentlayerPerson, - ContentlayerWebPageDocument, - ContentlayerWebsite, - Person, -} from './types/index.mjs'; -import { createIdentifierFromString } from './utils.mjs'; - -export type ComputeDTO = { - config: GalactiksConfig; - documents: T[]; - websites: ContentlayerWebsite[]; - people: ContentlayerPerson[]; -}; - -function createPage( - identifier: string, - document: Partial -) { - const id = createIdentifierFromString(identifier); - - return { - _id: id, - identifier: id, - slug: id, - description: '', - type: 'Page', - name: identifier, - ...document, - body: document.body?.raw - ? addBodyRender(document.body) - : { - raw: '', - code: '', - render: emptyRender, - }, - } as T; -} - -const createListingPage = ( - identifier: string, - document: Partial = {} -) => - createPage>( - identifier, - { - listingPage: true, - ...document, - } - ); - -const hydratePagesWithRender = async ( - documents: ContentlayerWebPageDocument[] -) => - documents.map((d) => ({ - ...d, - body: addBodyRender(d.body), - })); - -const computeRemainingListingPages = async ( - documents: ContentlayerWebPageDocumentWithRender[] -) => { - const getDocumentByIdentifier = documentByIdentifierSelector(documents); - - return documents.reduce((acc, _d) => { - const templateDocument: Partial = { - dateCreated: _d.dateCreated, - datePublished: _d.datePublished, - dateModified: _d.dateModified, - inLanguage: _d.inLanguage, - }; - - // If parent page does not exist, create it - if ( - _d.isPartOf && - acc.some( - (_a) => _a.identifier === _d.isPartOf && isInLanguage(_a, _d.inLanguage) - ) === false - ) { - let translationOfWork: Id | undefined = undefined; - if (_d.translationOfWork && _d.translationOfWork['@id']) { - const translationOfWorkDocument = getDocumentByIdentifier( - _d.translationOfWork['@id'] - ); - if (translationOfWorkDocument?.isPartOf) { - translationOfWork = { - type: 'Id', - '@id': translationOfWorkDocument.isPartOf, - }; - } - } - - acc = acc.concat( - createListingPage(_d.isPartOf, { - ...templateDocument, - translationOfWork, - }) - ); - } - - // Create all keywords pages not existing yet - if (Array.isArray(_d.keywords)) { - acc = acc.concat( - _d.keywords - .filter( - (_k) => - _k && - acc.some( - (_a) => _a.identifier === _k && isInLanguage(_a, _d.inLanguage) - ) === false - ) - .map((_k) => - createListingPage(_k, { - ...templateDocument, - type: 'Tag', - }) - ) - ); - } - - return acc; - }, documents); -}; - -const computeMissingFields = - (_: GalactiksConfig, people: ContentlayerPerson[]) => - async ( - documents: Array< - ContentlayerDocumentWithURL & ContentlayerWebPageDocumentWithRender - > - ): Promise => { - const buildBreadcrumb = breadcrumbBuilder(documents); - const buildAlternates = alternatesHeaderBuilder(documents); - const selectPersonByIdentifierAndLanguage = - documentByTypeAndIdentifierAndLanguageSelector('Person', documents); - - const getAuthor = ( - identifier?: string, - inLanguage?: string - ): Person | undefined => { - let author; - if (identifier) { - author = selectPersonByIdentifierAndLanguage(identifier, inLanguage); - } else if (people.length === 1) { - author = people[0]; - } - - return ( - author && { - identifier: author.identifier, - name: author.name, - description: author.description, - url: author.url, - image: author.image, - } - ); - }; - - return documents.map((document) => { - const dateCreated = new Date(document.dateCreated); - const contentWithoutHeaders: Omit = { - ...document, - author: getAuthor(document.author, document.inLanguage), - breadcrumb: buildBreadcrumb(document), - dateCreated, - dateModified: document.dateModified - ? new Date(document.dateModified) - : dateCreated, - datePublished: document.datePublished - ? new Date(document.datePublished) - : dateCreated, - }; - - return { - ...contentWithoutHeaders, - headers: { - ...getBasicHeaders(contentWithoutHeaders), - alternates: buildAlternates(contentWithoutHeaders), - structuredDataSchemas: getStructuredDataSchemas( - contentWithoutHeaders - ), - openGraph: getOpenGraphObjects(contentWithoutHeaders), - twitterCard: getTwitterCard(contentWithoutHeaders), - }, - }; - }); - }; - -export const computeDocuments = async ({ - config, - documents, - people, - websites, -}: ComputeDTO): Promise => - Promise.resolve(documents) - .then(hydratePagesWithRender) - .then(computeRemainingListingPages) - .then(computeDocumentsUrl(websites)) - .then(computeMissingFields(config, people)); diff --git a/packages/explorer/src/core/content/hydrate/common.mts b/packages/explorer/src/core/content/hydrate/common.mts new file mode 100644 index 00000000..efb34483 --- /dev/null +++ b/packages/explorer/src/core/content/hydrate/common.mts @@ -0,0 +1,27 @@ +import type { ContentlayerWebPageDocument } from '../../types/index.mjs' +import { createIdentifierFromString } from '../utils.mjs'; +import { addBodyRender, emptyRender } from './render.mjs'; + +export function createPage( + identifier: string, + document: Partial +) { + const id = createIdentifierFromString(identifier); + + return { + _id: id, + identifier: id, + slug: id, + description: '', + type: 'Page', + name: identifier, + ...document, + body: document.body?.raw + ? addBodyRender(document.body) + : { + raw: '', + code: '', + render: emptyRender, + }, + } as T; +} diff --git a/packages/explorer/src/core/content/hydrate/index.mts b/packages/explorer/src/core/content/hydrate/index.mts new file mode 100644 index 00000000..39d1448e --- /dev/null +++ b/packages/explorer/src/core/content/hydrate/index.mts @@ -0,0 +1,30 @@ +import type { Content, ContentlayerWebPageDocument } from '../../types/index.mjs'; +import type { ComputeDTO } from './types.mjs'; +import { computeRemainingListingPages } from './listing-pages.mjs'; +import { computeDocumentsUrl } from './urls.mjs'; +import { computeMissingFields } from './missing-fields.mjs'; +import { addBodyRender } from './render.mjs'; +import { createSameLanguagePages } from './same-language.mjs'; + +export type * from './types.mjs'; + +const hydratePagesWithRender = async ( + documents: ContentlayerWebPageDocument[] +) => + documents.map((d) => ({ + ...d, + body: addBodyRender(d.body), + })); + +export const computeDocuments = async ({ + config, + documents, + people, + websites, +}: ComputeDTO): Promise => + Promise.resolve(documents) + .then(hydratePagesWithRender) + .then(createSameLanguagePages(config)) + .then(computeRemainingListingPages()) + .then(computeDocumentsUrl(websites)) + .then(computeMissingFields(config, people)); diff --git a/packages/explorer/src/core/content/hydrate/listing-pages.mts b/packages/explorer/src/core/content/hydrate/listing-pages.mts new file mode 100644 index 00000000..8154bb75 --- /dev/null +++ b/packages/explorer/src/core/content/hydrate/listing-pages.mts @@ -0,0 +1,82 @@ +import type { Id } from '@galactiks/contentlayer'; +import { documentByIdentifierSelector, isInLanguage } from '../selectors.mjs'; +import type { ContentlayerWebPageDocument, ContentlayerDocumentWithRender, ContentlayerWebPageDocumentWithRender } from '../../types/index.mjs'; + +import { createPage } from './common.mjs'; + +const createListingPage = ( + identifier: string, + document: Partial = {} +) => + createPage>( + identifier, + { + listingPage: true, + ...document, + } + ); + +export const computeRemainingListingPages = () => async ( + documents: ContentlayerWebPageDocumentWithRender[] +) => { + const getDocumentByIdentifier = documentByIdentifierSelector(documents); + + return documents.reduce((acc, _d) => { + const templateDocument: Partial = { + dateCreated: _d.dateCreated, + datePublished: _d.datePublished, + dateModified: _d.dateModified, + inLanguage: _d.inLanguage, + }; + + // If parent page does not exist, create it + if ( + _d.isPartOf && + acc.some( + (_a) => _a.identifier === _d.isPartOf && isInLanguage(_a, _d.inLanguage) + ) === false + ) { + let translationOfWork: Id | undefined = undefined; + if (_d.translationOfWork && _d.translationOfWork['@id']) { + const translationOfWorkDocument = getDocumentByIdentifier( + _d.translationOfWork['@id'] + ); + if (translationOfWorkDocument?.isPartOf) { + translationOfWork = { + type: 'Id', + '@id': translationOfWorkDocument.isPartOf, + }; + } + } + + acc = acc.concat( + createListingPage(_d.isPartOf, { + ...templateDocument, + translationOfWork, + }) + ); + } + + // Create all keywords pages not existing yet + if (Array.isArray(_d.keywords)) { + acc = acc.concat( + _d.keywords + .filter( + (_k) => + _k && + acc.some( + (_a) => _a.identifier === _k && isInLanguage(_a, _d.inLanguage) + ) === false + ) + .map((_k) => + createListingPage(_k, { + ...templateDocument, + type: 'Tag', + }) + ) + ); + } + + return acc; + }, documents); +}; diff --git a/packages/explorer/src/core/content/hydrate/missing-fields.mts b/packages/explorer/src/core/content/hydrate/missing-fields.mts new file mode 100644 index 00000000..d5b89e08 --- /dev/null +++ b/packages/explorer/src/core/content/hydrate/missing-fields.mts @@ -0,0 +1,68 @@ +import type { GalactiksConfig } from '@galactiks/config'; +import type { Content, ContentlayerPerson, ContentlayerDocumentWithURL, ContentlayerWebPageDocumentWithRender, Person } from '../../types/index.mjs'; +import { documentByTypeAndIdentifierAndLanguageSelector } from '../selectors.mjs'; +import { alternatesHeaderBuilder, breadcrumbBuilder, getBasicHeaders, getOpenGraphObjects, getStructuredDataSchemas, getTwitterCard } from '../metadata/index.mjs'; + +export const computeMissingFields = + (_: GalactiksConfig, people: ContentlayerPerson[]) => + async ( + documents: Array< + ContentlayerDocumentWithURL & ContentlayerWebPageDocumentWithRender + > + ) => { + const buildBreadcrumb = breadcrumbBuilder(documents); + const buildAlternates = alternatesHeaderBuilder(documents); + const selectPersonByIdentifierAndLanguage = + documentByTypeAndIdentifierAndLanguageSelector('Person', documents); + + const getAuthor = ( + identifier?: string, + inLanguage?: string + ): Person | undefined => { + let author; + if (identifier) { + author = selectPersonByIdentifierAndLanguage(identifier, inLanguage); + } else if (people.length === 1) { + author = people[0]; + } + + return ( + author && { + identifier: author.identifier, + name: author.name, + description: author.description, + url: author.url, + image: author.image, + } + ); + }; + + return documents.map((document) => { + const dateCreated = new Date(document.dateCreated); + const contentWithoutHeaders: Omit = { + ...document, + author: getAuthor(document.author, document.inLanguage), + breadcrumb: buildBreadcrumb(document), + dateCreated, + dateModified: document.dateModified + ? new Date(document.dateModified) + : dateCreated, + datePublished: document.datePublished + ? new Date(document.datePublished) + : dateCreated, + }; + + return { + ...contentWithoutHeaders, + headers: { + ...getBasicHeaders(contentWithoutHeaders), + alternates: buildAlternates(contentWithoutHeaders), + structuredDataSchemas: getStructuredDataSchemas( + contentWithoutHeaders + ), + openGraph: getOpenGraphObjects(contentWithoutHeaders), + twitterCard: getTwitterCard(contentWithoutHeaders), + }, + }; + }); + }; diff --git a/packages/explorer/src/core/content/render.mts b/packages/explorer/src/core/content/hydrate/render.mts similarity index 54% rename from packages/explorer/src/core/content/render.mts rename to packages/explorer/src/core/content/hydrate/render.mts index d636d4a2..f4d05b7b 100644 --- a/packages/explorer/src/core/content/render.mts +++ b/packages/explorer/src/core/content/hydrate/render.mts @@ -1,17 +1,9 @@ -import type { MDX } from '@galactiks/contentlayer'; import { getMDXComponent } from 'mdx-bundler/client/index.js'; import * as React from 'react'; import type { ContentlayerDocumentTypes, - ContentlayerWebPageDocument, -} from './types/index.mjs'; - -export type Render = () => { Content: React.FC }; -export type ContentlayerDocumentWithRender = T & { - body: MDX & { render: Render }; -}; -export type ContentlayerWebPageDocumentWithRender = - ContentlayerDocumentWithRender; + Render, +} from '../../types/index.mjs'; export const emptyRender: Render = () => ({ Content: () => null }); diff --git a/packages/explorer/src/core/content/hydrate/same-language.mts b/packages/explorer/src/core/content/hydrate/same-language.mts new file mode 100644 index 00000000..b7941585 --- /dev/null +++ b/packages/explorer/src/core/content/hydrate/same-language.mts @@ -0,0 +1,49 @@ +import type { GalactiksConfig } from '@galactiks/config'; +import groupBy from 'lodash.groupby'; +import type { ContentlayerDocumentWithRender, ContentlayerWebPageDocument, ContentlayerWebPageDocumentWithRender } from '../../types/index.mjs'; +import { documentByIdentifierAndLanguageSelector } from '../selectors.mjs'; +import { createPage } from './common.mjs'; + +const groupLocalesByLanguage = (config: GalactiksConfig) => + groupBy(config.locales?.available ?? [], locale => new Intl.Locale(locale).language) + +const getLanguagesWithMultipleLocales = (config: GalactiksConfig) => Object.fromEntries( + Object.entries(groupLocalesByLanguage(config)).filter(([, locales]) => locales.length > 1) +) + +export const createSameLanguagePages = (config: GalactiksConfig) => { + const localesGroupedByLanguage = getLanguagesWithMultipleLocales(config) + const languagesWithMultipleLocales = Object.keys(localesGroupedByLanguage) + + return async ( + documents: ContentlayerWebPageDocumentWithRender[] + ) => { + if (languagesWithMultipleLocales.length === 0) { + return documents + } + const selectPageByIdentifierAndInLanguage = documentByIdentifierAndLanguageSelector(documents) + + return documents.reduce((acc, _d) => { + if (!_d.inLanguage) { + return acc + } + + const language = new Intl.Locale(_d.inLanguage).language + if (!languagesWithMultipleLocales.includes(language)) { + return acc + } + + acc = acc.concat( + localesGroupedByLanguage[language] + .filter(locale => locale !== _d.inLanguage) // Exclude current language + .filter(locale => selectPageByIdentifierAndInLanguage(_d.identifier, locale) === undefined) // Exclude already existing locale + .map(locale => createPage>(_d.identifier, { + ..._d, + inLanguage: locale + })) + ) + + return acc + }, documents) + }; +}; diff --git a/packages/explorer/src/core/content/hydrate/types.mts b/packages/explorer/src/core/content/hydrate/types.mts new file mode 100644 index 00000000..c2bb9154 --- /dev/null +++ b/packages/explorer/src/core/content/hydrate/types.mts @@ -0,0 +1,11 @@ +import type { GalactiksConfig } from '@galactiks/config'; +import type { Content, ContentlayerPerson, ContentlayerWebPageDocument, ContentlayerWebsite } from '../../types/index.mjs'; + +export type ComputeDTO = { + config: GalactiksConfig; + documents: T[]; + websites: ContentlayerWebsite[]; + people: ContentlayerPerson[]; +}; + +export type ComputeHandler = (config: GalactiksConfig, websites: ContentlayerWebsite[], people: ContentlayerPerson[]) => (documents: ContentlayerWebPageDocument) => Promise diff --git a/packages/explorer/src/core/content/urls.mts b/packages/explorer/src/core/content/hydrate/urls.mts similarity index 90% rename from packages/explorer/src/core/content/urls.mts rename to packages/explorer/src/core/content/hydrate/urls.mts index 40b7168e..5cef8ac4 100644 --- a/packages/explorer/src/core/content/urls.mts +++ b/packages/explorer/src/core/content/hydrate/urls.mts @@ -4,30 +4,21 @@ import { documentTypes } from '@galactiks/contentlayer'; import { join } from 'path'; import Debug from 'debug'; -import { homeIdentifier } from './consts.mjs'; +import { homeIdentifier } from '../consts.mjs'; import { documentByIdentifierAndLanguageSelector, documentsByLanguageSelector, pageDepthSelector, -} from './selectors.mjs'; -import type { ContentlayerWebPageDocumentWithRender } from './render.mjs'; +} from '../selectors.mjs'; import type { + ContentlayerDocumentWithURL, ContentlayerWebPageDocument, + ContentlayerWebPageDocumentWithRender, ContentlayerWebsite, -} from './types/index.mjs'; +} from '../../types/index.mjs'; const debug = Debug('@galactiks/explorer:urls'); -type WithRequired = T & { [P in K]-?: T[P] }; -type ContentlayerDocumentWithPath = WithRequired< - ContentlayerWebPageDocument, - 'path' ->; -export type ContentlayerDocumentWithURL = WithRequired< - ContentlayerDocumentWithPath, - 'url' ->; - const _getPathWithoutTemplate = ( document: ContentlayerWebPageDocument ): string => { diff --git a/packages/explorer/src/core/content/index.mts b/packages/explorer/src/core/content/index.mts index 7a7a46fa..1a0bcb3c 100644 --- a/packages/explorer/src/core/content/index.mts +++ b/packages/explorer/src/core/content/index.mts @@ -1,2 +1,2 @@ +export type * from '../types/index.mjs'; export * from './repositories/index.mjs'; -export * from './types/index.mjs'; diff --git a/packages/explorer/src/core/content/metadata/alternates.mts b/packages/explorer/src/core/content/metadata/alternates.mts index 9b5af927..63fff8cd 100644 --- a/packages/explorer/src/core/content/metadata/alternates.mts +++ b/packages/explorer/src/core/content/metadata/alternates.mts @@ -1,6 +1,5 @@ import { getDefaultLanguage } from '@galactiks/config'; -import type { Content, MetadataHeaders } from '../index.mjs'; -import type { ContentlayerDocumentWithURL } from '../urls.mjs'; +import type { Content, ContentlayerDocumentWithURL, MetadataHeaders } from '../../types/index.mjs'; export const alternatesHeaderBuilder = ( documents: ContentlayerDocumentWithURL[] diff --git a/packages/explorer/src/core/content/metadata/breadcrumb.mts b/packages/explorer/src/core/content/metadata/breadcrumb.mts index 8be16824..8164815a 100644 --- a/packages/explorer/src/core/content/metadata/breadcrumb.mts +++ b/packages/explorer/src/core/content/metadata/breadcrumb.mts @@ -1,6 +1,5 @@ -import type { ItemList } from '../types/index.mjs'; +import type { ContentlayerDocumentWithURL, ItemList } from '../../types/index.mjs'; import { homeIdentifier, pageDepthLimit } from '../consts.mjs'; -import type { ContentlayerDocumentWithURL } from '../urls.mjs'; import { documentByIdentifierAndLanguageSelector } from '../selectors.mjs'; import { MaxDepthLimitReachedError } from '../../exceptions/index.mjs'; diff --git a/packages/explorer/src/core/content/metadata/headers.mts b/packages/explorer/src/core/content/metadata/headers.mts index 4aae5b90..d741fe52 100644 --- a/packages/explorer/src/core/content/metadata/headers.mts +++ b/packages/explorer/src/core/content/metadata/headers.mts @@ -1,4 +1,4 @@ -import type { Content, MetadataHeaders } from '../types/index.mjs'; +import type { Content, MetadataHeaders } from '../../types/index.mjs'; export const getBasicHeaders = (entry: Content): MetadataHeaders => ({ title: entry.name, diff --git a/packages/explorer/src/core/content/metadata/ogp/article.mts b/packages/explorer/src/core/content/metadata/ogp/article.mts index 74efdea2..86330e1b 100644 --- a/packages/explorer/src/core/content/metadata/ogp/article.mts +++ b/packages/explorer/src/core/content/metadata/ogp/article.mts @@ -1,4 +1,4 @@ -import type { Content, MetadataHeaders } from '../../types/index.mjs'; +import type { Content, MetadataHeaders } from '../../../types/index.mjs'; export const getArticle = (document: Content): MetadataHeaders['openGraph'] => { const headers = [ diff --git a/packages/explorer/src/core/content/metadata/ogp/og.mts b/packages/explorer/src/core/content/metadata/ogp/og.mts index d789f9fd..c371a0b0 100644 --- a/packages/explorer/src/core/content/metadata/ogp/og.mts +++ b/packages/explorer/src/core/content/metadata/ogp/og.mts @@ -1,4 +1,4 @@ -import type { Content, MetadataHeaders } from '../../types/index.mjs'; +import type { Content, MetadataHeaders } from '../../../types/index.mjs'; import { getArticle } from './article.mjs'; import { getWebsite } from './website.mjs'; diff --git a/packages/explorer/src/core/content/metadata/ogp/website.mts b/packages/explorer/src/core/content/metadata/ogp/website.mts index 58e097c6..c10d9002 100644 --- a/packages/explorer/src/core/content/metadata/ogp/website.mts +++ b/packages/explorer/src/core/content/metadata/ogp/website.mts @@ -1,4 +1,4 @@ -import type { Content, MetadataHeaders } from '../../types/index.mjs'; +import type { Content, MetadataHeaders } from '../../../types/index.mjs'; export const getWebsite = (document: Content): MetadataHeaders['openGraph'] => { const headers = [ diff --git a/packages/explorer/src/core/content/metadata/schemas/article.mts b/packages/explorer/src/core/content/metadata/schemas/article.mts index bfe40bea..8897d063 100644 --- a/packages/explorer/src/core/content/metadata/schemas/article.mts +++ b/packages/explorer/src/core/content/metadata/schemas/article.mts @@ -1,5 +1,5 @@ import type { Article, WithContext } from 'schema-dts'; -import type { Content } from '../../types/index.mjs'; +import type { Content } from '../../../types/index.mjs'; import { getPerson } from './person.mjs'; export const getArticle = (document: Content): WithContext
=> ({ diff --git a/packages/explorer/src/core/content/metadata/schemas/breadcrumb.mts b/packages/explorer/src/core/content/metadata/schemas/breadcrumb.mts index 14c3721a..ff973164 100644 --- a/packages/explorer/src/core/content/metadata/schemas/breadcrumb.mts +++ b/packages/explorer/src/core/content/metadata/schemas/breadcrumb.mts @@ -1,5 +1,5 @@ import type { BreadcrumbList, WithContext } from 'schema-dts'; -import type { Content } from '../../types/index.mjs'; +import type { Content } from '../../../types/index.mjs'; export const getBreadcrumb = ( document: Content diff --git a/packages/explorer/src/core/content/metadata/schemas/organization.mts b/packages/explorer/src/core/content/metadata/schemas/organization.mts index 185fb9ac..e0148ca5 100644 --- a/packages/explorer/src/core/content/metadata/schemas/organization.mts +++ b/packages/explorer/src/core/content/metadata/schemas/organization.mts @@ -1,5 +1,5 @@ import type { Organization, WithContext } from 'schema-dts'; -import type { Organization as ContentOrganization } from '../../types/index.mjs'; +import type { Organization as ContentOrganization } from '../../../types/index.mjs'; export const getOrganization = ( document: ContentOrganization diff --git a/packages/explorer/src/core/content/metadata/schemas/person.mts b/packages/explorer/src/core/content/metadata/schemas/person.mts index a8ae8dc5..07202977 100644 --- a/packages/explorer/src/core/content/metadata/schemas/person.mts +++ b/packages/explorer/src/core/content/metadata/schemas/person.mts @@ -1,5 +1,5 @@ import type { Person, WithContext } from 'schema-dts'; -import type { Person as ContentPerson } from '../../types/index.mjs'; +import type { Person as ContentPerson } from '../../../types/index.mjs'; export const getPerson = (document: ContentPerson): WithContext => ({ '@context': 'https://schema.org', diff --git a/packages/explorer/src/core/content/metadata/schemas/structured-data.mts b/packages/explorer/src/core/content/metadata/schemas/structured-data.mts index 7dbd923e..61bd686b 100644 --- a/packages/explorer/src/core/content/metadata/schemas/structured-data.mts +++ b/packages/explorer/src/core/content/metadata/schemas/structured-data.mts @@ -1,6 +1,6 @@ import type { Thing, WithContext } from 'schema-dts'; -import type { Content } from '../../types/index.mjs'; +import type { Content } from '../../../types/index.mjs'; import { getArticle } from './article.mjs'; import { getBreadcrumb } from './breadcrumb.mjs'; diff --git a/packages/explorer/src/core/content/metadata/twitter.mts b/packages/explorer/src/core/content/metadata/twitter.mts index 60478a41..e56cb6a9 100644 --- a/packages/explorer/src/core/content/metadata/twitter.mts +++ b/packages/explorer/src/core/content/metadata/twitter.mts @@ -1,4 +1,4 @@ -import type { Content, MetadataHeaders } from '../index.mjs'; +import type { Content, MetadataHeaders } from '../../types/index.mjs'; export const getTwitterCard = ( document: Content diff --git a/packages/explorer/src/core/content/repositories/filters.mts b/packages/explorer/src/core/content/repositories/filters.mts index 283af1d5..44c74f00 100644 --- a/packages/explorer/src/core/content/repositories/filters.mts +++ b/packages/explorer/src/core/content/repositories/filters.mts @@ -1,4 +1,4 @@ -import type { Content } from '../types/index.mjs'; +import type { Content } from '../../types/index.mjs'; export type RepositoryFilters = { type?: Content['type']; diff --git a/packages/explorer/src/core/content/repositories/generated.mts b/packages/explorer/src/core/content/repositories/generated.mts index 93a59251..00f97a5b 100644 --- a/packages/explorer/src/core/content/repositories/generated.mts +++ b/packages/explorer/src/core/content/repositories/generated.mts @@ -6,8 +6,8 @@ import type { ContentlayerWebPageElement, ContentlayerWebPageDocument, ContentlayerOrganization, -} from '../types/index.mjs'; -import { computeDocuments } from '../compute.mjs'; +} from '../../types/index.mjs'; +import { computeDocuments } from '../hydrate/index.mjs'; import type { RepositoryFilters, WebPageElementFilters } from './filters.mjs'; diff --git a/packages/explorer/src/core/content/repositories/index.mts b/packages/explorer/src/core/content/repositories/index.mts index 5d7de262..3f180610 100644 --- a/packages/explorer/src/core/content/repositories/index.mts +++ b/packages/explorer/src/core/content/repositories/index.mts @@ -1,11 +1,12 @@ -import type { Content } from '../types/index.mjs'; +import { getDefaultLanguage, getLanguages } from '@galactiks/config'; + +import type { Content } from '../../types/index.mjs'; import { homeIdentifier } from '../consts.mjs'; import { documentByIdentifierSelector } from '../selectors.mjs'; import { createIdentifierFromString } from '../utils.mjs'; import type { RepositoryFilters } from './filters.mjs'; import { getOrganizations, getPages } from './generated.mjs'; -import { getDefaultLanguage, getLanguages } from '@galactiks/config'; export * from './generated.mjs'; diff --git a/packages/explorer/src/core/content/selectors.mts b/packages/explorer/src/core/content/selectors.mts index 94c43204..2b422d1e 100644 --- a/packages/explorer/src/core/content/selectors.mts +++ b/packages/explorer/src/core/content/selectors.mts @@ -1,9 +1,9 @@ import { MaxDepthLimitReachedError } from '../exceptions/index.mjs'; -import { pageDepthLimit } from './consts.mjs'; import type { ContentlayerDocumentTypes, ContentlayerWebPageDocument, -} from './types/index.mjs'; +} from '../types/index.mjs'; +import { pageDepthLimit } from './consts.mjs'; export const isInLanguage = ( d: Pick, diff --git a/packages/explorer/src/core/content/types/_schemas.mts b/packages/explorer/src/core/types/_schemas.mts similarity index 100% rename from packages/explorer/src/core/content/types/_schemas.mts rename to packages/explorer/src/core/types/_schemas.mts diff --git a/packages/explorer/src/core/content/types/index.mts b/packages/explorer/src/core/types/index.mts similarity index 65% rename from packages/explorer/src/core/content/types/index.mts rename to packages/explorer/src/core/types/index.mts index 16151188..7cfca37c 100644 --- a/packages/explorer/src/core/content/types/index.mts +++ b/packages/explorer/src/core/types/index.mts @@ -7,7 +7,7 @@ import type { } from '@galactiks/contentlayer'; import type { MetadataHeaders, Page } from './_schemas.mjs'; -import type { Render } from '../render.mjs'; +import type { ContentlayerDocumentWithRender, Render } from './render.mjs'; export type Content = Page & { body: MDX & { render: Render }; @@ -23,6 +23,8 @@ export type { WebPageElement as ContentlayerWebPageElement, } from '@galactiks/contentlayer'; export type * from './_schemas.mjs'; +export type * from './render.mjs'; + type ContentlayerTagPage = Omit & { type: 'Tag' }; export type ContentlayerWebPageDocument = | ContentlayerArticle @@ -30,3 +32,15 @@ export type ContentlayerWebPageDocument = | ContentlayerPerson | ContentlayerPlace | ContentlayerTagPage; +export type ContentlayerWebPageDocumentWithRender = + ContentlayerDocumentWithRender; + +type WithRequired = T & { [P in K]-?: T[P] }; +type ContentlayerDocumentWithPath = WithRequired< + ContentlayerWebPageDocument, + 'path' +>; +export type ContentlayerDocumentWithURL = WithRequired< + ContentlayerDocumentWithPath, + 'url' +>; diff --git a/packages/explorer/src/core/types/render.mts b/packages/explorer/src/core/types/render.mts new file mode 100644 index 00000000..6d32bda7 --- /dev/null +++ b/packages/explorer/src/core/types/render.mts @@ -0,0 +1,6 @@ +import type { MDX } from '@galactiks/contentlayer'; + +export type Render = () => { Content: React.FC }; +export type ContentlayerDocumentWithRender = T & { + body: MDX & { render: Render }; +}; diff --git a/packages/explorer/tsconfig.json b/packages/explorer/tsconfig.json index 1421f3cd..793c5299 100644 --- a/packages/explorer/tsconfig.json +++ b/packages/explorer/tsconfig.json @@ -7,6 +7,11 @@ "target": "ES2021", "module": "ES2022", "declarationDir": "./dist", - "outDir": "./dist" + "outDir": "./dist", + "paths": { + "@/*": ["./*"], + "@content/*": ["./content/*"], + "@exceptions/*": ["./exceptions/*"], + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29811212..a93aafd5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: false - excludeLinksFromLockfile: false - overrides: '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.13.0 @@ -154,6 +150,9 @@ importers: esbuild: specifier: 0.19.11 version: 0.19.11 + lodash.groupby: + specifier: ^4.6.0 + version: 4.6.0 mdx-bundler: specifier: 10.0.1 version: 10.0.1(esbuild@0.19.11) @@ -170,6 +169,9 @@ importers: '@types/debug': specifier: 4.1.12 version: 4.1.12 + '@types/lodash.groupby': + specifier: ^4.6.9 + version: 4.6.9 '@types/react': specifier: 18.2.46 version: 18.2.46 @@ -874,10 +876,10 @@ packages: resolution: {integrity: sha512-47gAg0O2pW5Jlo86jfzjdkwL5a7Bzb+Kj5WTmdu4CxYRfWn9ytKjuuYIfsNDW8neuhdKzn+P5wCddgEh0glYyQ==} peerDependencies: '@effect-ts/core': ^0.60.2 - '@opentelemetry/api': 1.4.1 - '@opentelemetry/core': 1.13.0 - '@opentelemetry/exporter-trace-otlp-grpc': 0.39.1 - '@opentelemetry/sdk-trace-base': 1.13.0 + '@opentelemetry/api': ^1.4.0 + '@opentelemetry/core': ^1.13.0 + '@opentelemetry/exporter-trace-otlp-grpc': ^0.39.0 + '@opentelemetry/sdk-trace-base': ^1.13.0 dependencies: '@effect-ts/core': 0.60.5 '@effect-ts/otel': 0.15.1(@effect-ts/core@0.60.5)(@opentelemetry/api@1.4.1)(@opentelemetry/core@1.13.0)(@opentelemetry/sdk-trace-base@1.13.0) @@ -891,10 +893,10 @@ packages: resolution: {integrity: sha512-a2sF0ylmn8xOJs8fNeT/spJ1gUcsksAJCALxo9WOfuTCMtTwMVtVhCKEPEeQoL7wFqU+JgPkVdP91+FJ/Rkeow==} peerDependencies: '@effect-ts/core': ^0.60.2 - '@opentelemetry/api': 1.4.1 - '@opentelemetry/core': 1.13.0 - '@opentelemetry/sdk-trace-base': 1.13.0 - '@opentelemetry/sdk-trace-node': 1.13.0 + '@opentelemetry/api': ^1.4.0 + '@opentelemetry/core': ^1.13.0 + '@opentelemetry/sdk-trace-base': ^1.13.0 + '@opentelemetry/sdk-trace-node': ^1.13.0 dependencies: '@effect-ts/core': 0.60.5 '@effect-ts/otel': 0.15.1(@effect-ts/core@0.60.5)(@opentelemetry/api@1.4.1)(@opentelemetry/core@1.13.0)(@opentelemetry/sdk-trace-base@1.13.0) @@ -908,9 +910,9 @@ packages: resolution: {integrity: sha512-AmZJHl7t0+Peh7Yb2+hqn6r9+rd9/UfeA4AMV9h0YGTdOyouyFfD3wzWlxnAUzAQ4Lrod4kC7Noruret4EpqpA==} peerDependencies: '@effect-ts/core': ^0.60.2 - '@opentelemetry/api': 1.4.1 - '@opentelemetry/core': 1.13.0 - '@opentelemetry/sdk-trace-base': 1.13.0 + '@opentelemetry/api': ^1.4.0 + '@opentelemetry/core': ^1.13.0 + '@opentelemetry/sdk-trace-base': ^1.13.0 dependencies: '@effect-ts/core': 0.60.5 '@opentelemetry/api': 1.4.1 @@ -926,7 +928,7 @@ packages: resolution: {integrity: sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==} requiresBuild: true dependencies: - tslib: 2.5.0 + tslib: 2.6.2 dev: true optional: true @@ -1788,7 +1790,7 @@ packages: resolution: {integrity: sha512-pS5fU4lrRjOIPZQqA2V1SUM9QUFXbO+8flubAiy6ntLjnAjJJUdRFOUOxK6v86ZHI2p2S8A0vD0BTu95FZYvjA==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': '>=1.0.0 <1.5.0' dependencies: '@opentelemetry/api': 1.4.1 dev: true @@ -1797,7 +1799,7 @@ packages: resolution: {integrity: sha512-2dBX3Sj99H96uwJKvc2w9NOiNgbvAO6mOFJFramNkKfS9O4Um+VWgpnlAazoYjT6kUJ1MP70KQ5ngD4ed+4NUw==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': '>=1.0.0 <1.5.0' dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/semantic-conventions': 1.13.0 @@ -1807,7 +1809,7 @@ packages: resolution: {integrity: sha512-l5RhLKx6U+yuLhMrtgavTDthX50E1mZM3/SSySC7OPZiArFHV/b/9x9jxAzrOgIQUDxyj4N0V9aLKSA2t7Qzxg==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': ^1.0.0 dependencies: '@grpc/grpc-js': 1.8.18 '@opentelemetry/api': 1.4.1 @@ -1822,7 +1824,7 @@ packages: resolution: {integrity: sha512-Pv5X8fbi6jD/RJBePyn7MnCSuE6MbPB6dl+7YYBWJ5RcMGYMwvLXjd4h2jWsPV2TSUg38H/RoSP0aXvQ06Y7iw==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': ^1.0.0 dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.13.0(@opentelemetry/api@1.4.1) @@ -1832,7 +1834,7 @@ packages: resolution: {integrity: sha512-u3ErFRQqQFKjjIMuwLWxz/tLPYInfmiAmSy//fGSCzCh2ZdJgqQjMOAxBgqFtCF2xFL+OmMhyuC2ThMzceGRWA==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': ^1.0.0 dependencies: '@grpc/grpc-js': 1.8.18 '@opentelemetry/api': 1.4.1 @@ -1845,7 +1847,7 @@ packages: resolution: {integrity: sha512-0hgVnXXz5efI382B/24NxD4b6Zxlh7nxCdJkxkdmQMbn0yRiwoq/ZT+QG8eUL6JNzsBAV1WJlF5aJNsL8skHvw==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': '>=1.3.0 <1.5.0' dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/api-logs': 0.39.1 @@ -1860,7 +1862,7 @@ packages: resolution: {integrity: sha512-HOo91EI4UbuG8xQVLFziTzrcIn0MJQhy8m9jorh8aonb94jFVFi3CFNIiAnIGOabmnshJLOABxpYXsiPB8Xnzg==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': '>=1.0.0 <1.5.0' dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.13.0(@opentelemetry/api@1.4.1) @@ -1870,7 +1872,7 @@ packages: resolution: {integrity: sha512-IV9TO+u1Jzm9mUDAD3gyXf89eyvgEJUY1t+GB5QmS4wjVeWrSMUtD0JjH3yG9SNqkrQOqOGJq7YUSSetW+Lf5Q==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': '>=1.0.0 <1.5.0' dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.13.0(@opentelemetry/api@1.4.1) @@ -1880,7 +1882,7 @@ packages: resolution: {integrity: sha512-euqjOkiN6xhjE//0vQYGvbStxoD/WWQRhDiO0OTLlnLBO9Yw2Gd/VoSx2H+svsebjzYk5OxLuREBmcdw6rbUNg==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': '>=1.0.0 <1.5.0' dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.13.0(@opentelemetry/api@1.4.1) @@ -1891,7 +1893,7 @@ packages: resolution: {integrity: sha512-/gmgKfZ1ZVFporKuwsewqIyvaUIGpv76JZ7lBpHQQPb37IMpaXO6pdqFI4ebHAWfNIm3akMyhmdtzivcgF3lgw==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': '>=1.4.0 <1.5.0' '@opentelemetry/api-logs': '>=0.38.0' dependencies: '@opentelemetry/api': 1.4.1 @@ -1904,7 +1906,7 @@ packages: resolution: {integrity: sha512-MOjZX6AnSOqLliCcZUrb+DQKjAWXBiGeICGbHAGe5w0BB18PJIeIo995lO5JSaFfHpmUMgJButTPfJJD27W3Vg==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': '>=1.3.0 <1.5.0' dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.13.0(@opentelemetry/api@1.4.1) @@ -1916,7 +1918,7 @@ packages: resolution: {integrity: sha512-moTiQtc0uPR1hQLt6gLDJH9IIkeBhgRb71OKjNHZPE1VF45fHtD6nBDi5J/DkTHTwYP5X3kBJLa3xN7ub6J4eg==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': '>=1.0.0 <1.5.0' dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.13.0(@opentelemetry/api@1.4.1) @@ -1928,7 +1930,7 @@ packages: resolution: {integrity: sha512-FXA85lXKTsnbOflA/TBuBf2pmhD3c8uDjNjG0YqK+ap8UayfALmfJhf+aG1yBOUHevCY0JXJ4/xtbXExxpsMog==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': 1.4.1 + '@opentelemetry/api': '>=1.0.0 <1.5.0' dependencies: '@opentelemetry/api': 1.4.1 '@opentelemetry/context-async-hooks': 1.13.0(@opentelemetry/api@1.4.1) @@ -2171,6 +2173,12 @@ packages: resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} dev: true + /@types/lodash.groupby@4.6.9: + resolution: {integrity: sha512-z2xtCX2ko7GrqORnnYea4+ksT7jZNAvaOcLd6mP9M7J09RHvJs06W8BGdQQAX8ARef09VQLdeRilSOcfHlDQJQ==} + dependencies: + '@types/lodash': 4.14.198 + dev: true + /@types/lodash.set@4.3.9: resolution: {integrity: sha512-KOxyNkZpbaggVmqbpr82N2tDVTx05/3/j0f50Es1prxrWB0XYf9p3QNxqcbWb7P1Q9wlvsUSlCFnwlPCIJ46PQ==} dependencies: @@ -4839,6 +4847,10 @@ packages: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} dev: true + /lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + dev: false + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -8237,3 +8249,7 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +settings: + autoInstallPeers: false + excludeLinksFromLockfile: false