presentation/renderers/schema.renderer.ts
presentation/renderers/schema.renderer.ts is a file in Bane's Lab Site. 169 lines of code and 26 definitions.
import type { Crumb, SchemaEntity, SchemaGraph } from "#types/schema.types";
import {
DATASET_TYPE,
ORGANIZATION,
ORGANIZATION_ID,
PERSON,
PERSON_ID,
PROFILE_PAGE_TYPE,
SCHEMA_CONTEXT,
SCHEMA_LANGUAGE,
WEBSITE,
} from "#configuration/constants/schema.constants";
import { HOME_TITLE, TITLE_SEPARATOR } from "#configuration/strings/page.strings";
import type {
PageDefinition,
RecordSubject,
SchemaSubject,
SectionSubject,
ServedSubject,
SourceSubject,
} from "#types/page.types";
import {
SITE_URL,
markdownTwinPath,
pageIndexPath,
pagePath,
payloadTwinPath,
sectionLeafPath,
sectionMarkdownPath,
} from "#core/assets/link.assets";
import type { Section, Tab } from "#types/document.types";
import { basePage, breadcrumb, datesOf, encoding, instantOf } from "#core/factories/schema.factory";
import { ANCHOR_PREFIX } from "#configuration/constants/document.constants";
import { HOME_PAGE } from "#core/ids/page.ids";
import { JSON_FORMAT } from "#configuration/constants/code.constants";
import { hasOwnSections } from "#presentation/renderers/tab.renderer";
const crumbsOf = function crumbsOf(definition: PageDefinition, tab: Tab | undefined, path: string): Crumb[] {
const home: Crumb = { name: HOME_TITLE, path: pagePath(HOME_PAGE) };
if (definition.id === HOME_PAGE) {
return [home];
}
const page: Crumb = { name: definition.title, path: pagePath(definition.id) };
return tab === undefined ? [home, page] : [home, page, { name: tab.label, path }];
};
const sectionsOf = function sectionsOf(subject: SchemaSubject): readonly Section[] {
const content = subject.definition?.content;
if (content?.kind === "document") {
return content.sections;
}
const tab = content?.kind === "tabbed" ? (subject.tab ?? content.tabs[0]) : undefined;
return content?.kind !== "tabbed" || tab === undefined || hasOwnSections(content.layout, tab) ? [] : tab.sections;
};
const tabIdOf = function tabIdOf(subject: SchemaSubject): string | null {
const content = subject.definition?.content;
return content?.kind === "tabbed" ? ((subject.tab ?? content.tabs[0])?.id ?? null) : null;
};
const partsOf = function partsOf(subject: SchemaSubject): readonly SchemaEntity[] {
const page = subject.definition?.id ?? "";
const tab = tabIdOf(subject);
return sectionsOf(subject).map((section) => {
const url = SITE_URL + subject.path + ANCHOR_PREFIX + section.id;
return {
"@id": url,
"@type": "WebPageElement",
encoding: encoding(sectionLeafPath(page, tab, section.id), sectionMarkdownPath(page, tab, section.id)),
name: section.title,
url,
};
});
};
const articleOf = function articleOf(subject: SchemaSubject, definition: PageDefinition): SchemaEntity | null {
if (definition.article !== true || subject.banner === undefined) {
return null;
}
return {
"@type": "TechArticle",
author: { "@id": PERSON_ID },
...datesOf(subject.dates),
headline:
subject.tab === undefined ? definition.title : subject.tab.label + TITLE_SEPARATOR + definition.title,
image: subject.banner.stills.map((still) => SITE_URL + still),
inLanguage: SCHEMA_LANGUAGE,
publisher: { "@id": ORGANIZATION_ID },
};
};
const webPage = function webPage(subject: SchemaSubject): SchemaEntity {
const page = basePage(subject);
if (subject.definition === undefined) {
return page;
}
const parts = partsOf(subject);
const index = pageIndexPath(subject.definition.id);
const profile = subject.definition.profile === true;
const main = profile ? { "@id": PERSON_ID } : articleOf(subject, subject.definition);
return {
...page,
...(profile ? { "@type": PROFILE_PAGE_TYPE } : {}),
breadcrumb: breadcrumb(crumbsOf(subject.definition, subject.tab, subject.path)),
encoding: [
...encoding(payloadTwinPath(subject.path), markdownTwinPath(subject.path)),
{ "@type": "MediaObject", contentUrl: SITE_URL + index, encodingFormat: JSON_FORMAT },
],
...(parts.length === 0 ? {} : { hasPart: parts }),
...(main === null ? {} : { mainEntity: main }),
};
};
const sourceCode = function sourceCode(subject: SourceSubject): SchemaEntity {
return {
"@type": "SoftwareSourceCode",
isPartOf: { "@id": SITE_URL + subject.tabPath },
name: subject.name,
...(subject.language === null ? {} : { programmingLanguage: subject.language }),
...(subject.repository === null ? {} : { codeRepository: subject.repository }),
...(subject.license === null ? {} : { license: subject.license }),
};
};
const servedPage = function servedPage(
subject: ServedSubject,
parents: readonly Crumb[],
mainEntity: SchemaEntity,
): SchemaGraph {
const page: SchemaEntity = {
...basePage(subject),
breadcrumb: breadcrumb([
{ name: HOME_TITLE, path: pagePath(HOME_PAGE) },
...parents,
{ name: subject.name, path: subject.path },
]),
encoding: encoding(subject.alternates.json, subject.alternates.markdown),
mainEntity,
};
return { "@context": SCHEMA_CONTEXT, "@graph": [ORGANIZATION, PERSON, WEBSITE, page] };
};
export const sourceSchemaOf = function sourceSchemaOf(subject: SourceSubject): SchemaGraph {
const parents = subject.trail.map((link) => ({ name: link.label, path: link.path }));
return servedPage(subject, parents, sourceCode(subject));
};
export const recordSchemaOf = function recordSchemaOf(subject: RecordSubject): SchemaGraph {
const { collection } = subject;
return servedPage(
subject,
[{ name: collection.label, path: collection.path }],
{
"@type": "DefinedTerm",
...(subject.code === null ? {} : { termCode: subject.code }),
...(subject.summary === null ? {} : { description: subject.summary }),
inDefinedTermSet: { "@type": "DefinedTermSet", name: collection.label, url: SITE_URL + collection.path },
name: subject.name,
url: SITE_URL + subject.path,
},
);
};
export const sectionSchemaOf = function sectionSchemaOf(subject: SectionSubject): SchemaGraph {
const { seed } = subject;
return servedPage(
subject,
[{ name: seed.tabTitle, path: seed.tabPath }],
{ "@type": "ItemList", name: subject.name, numberOfItems: seed.records },
);
};
export const schemaOf = function schemaOf(subject: SchemaSubject): SchemaGraph {
const { dates } = subject;
const entities = (subject.definition?.entities ?? []).map((entity) =>
dates === undefined || entity["@type"] !== DATASET_TYPE
? entity
: { ...entity, dateModified: instantOf(dates.modified) },
);
return { "@context": SCHEMA_CONTEXT, "@graph": [ORGANIZATION, PERSON, WEBSITE, webPage(subject), ...entities] };
};