presentation/renderers/page.renderer.ts
presentation/renderers/page.renderer.ts is a file in Bane's Lab Site. 196 lines of code and 38 definitions.
import { ANATOMY_PAGE, HOME_PAGE, ONTOLOGY_PAGE } from "#core/ids/page.ids";
import { MISSING_PAGE_MESSAGE, MISSING_PAGE_TITLE, SITE_NAME, TITLE_SEPARATOR } from "#configuration/strings/page.strings";
import {
OPEN_GRAPH_ARTICLE,
OPEN_GRAPH_WEBSITE,
ROBOTS_INDEX,
ROBOTS_NOINDEX,
} from "#configuration/constants/document.constants";
import type {
PageDates,
PageDefinition,
PageHead,
RecordSubject,
SectionSubject,
SourceSubject,
} from "#types/page.types";
import { SITE_URL, markdownTwinPath, pagePath, payloadTwinPath, tabFromPath, tabLink } from "#core/assets/link.assets";
import { recordSchemaOf, schemaOf, sectionSchemaOf, sourceSchemaOf } from "#presentation/renderers/schema.renderer";
import { HOME_THESIS } from "#configuration/strings/home.strings";
import { MISSING_SHARE } from "#configuration/strings/report.strings";
import { SHARE_IMAGES } from "#core/generated/share.generated";
import type { ShareImage } from "#types/card.types";
import type { Tab } from "#types/document.types";
import { summarizeMarkup } from "#core/converters/markup.converter";
const TITLE_SLOTS = ['meta[property="og:title"]', 'meta[name="twitter:title"]'];
const IMAGE_SLOTS = ['meta[property="og:image"]', 'meta[name="twitter:image"]'];
const IMAGE_ALT_SLOTS = ['meta[property="og:image:alt"]', 'meta[name="twitter:image:alt"]'];
const DESCRIPTION_SLOTS = [
'meta[name="description"]',
'meta[property="og:description"]',
'meta[name="twitter:description"]',
];
const URL_SLOTS = ['meta[property="og:url"]'];
const KIND_SLOTS = ['meta[property="og:type"]'];
const ROBOTS_SLOTS = ['meta[name="robots"]'];
const CANONICAL_SLOT = 'link[rel="canonical"]';
const NONCE_SLOT = 'meta[property="csp-nonce"]';
const CONTENT_ATTRIBUTE = "content";
const MARKDOWN_ALTERNATE_SLOT = 'link[rel="alternate"][type="text/markdown"]';
const JSON_ALTERNATE_SLOT = 'link[rel="alternate"][type="application/json"]';
const SCHEMA_SLOT = 'script[type="application/ld+json"]';
const SCHEMA_INDENT = 2;
const tabText = function tabText(tab: Tab): string {
const [section] = tab.sections;
return section?.intro ?? section?.subsections[0]?.content ?? "";
};
const shareOf = function shareOf(page: string): ShareImage {
const share = SHARE_IMAGES.get(page);
if (share === undefined) {
throw new Error(MISSING_SHARE + page);
}
return share;
};
const pageHead = function pageHead(definition: PageDefinition, dates?: PageDates): PageHead {
const title =
definition.id === HOME_PAGE
? SITE_NAME + TITLE_SEPARATOR + HOME_THESIS
: definition.title + TITLE_SEPARATOR + SITE_NAME;
const banner = shareOf(definition.id);
const subject = {
banner,
...(dates === undefined ? {} : { dates }),
definition,
description: definition.description,
path: pagePath(definition.id),
title,
};
const kind = definition.id === HOME_PAGE ? OPEN_GRAPH_WEBSITE : OPEN_GRAPH_ARTICLE;
return { ...subject, kind, robots: ROBOTS_INDEX, schema: schemaOf(subject) };
};
export const tabHead = function tabHead(definition: PageDefinition, tab: Tab, dates?: PageDates): PageHead {
const subject = {
banner: shareOf(definition.id),
...(dates === undefined ? {} : { dates }),
definition,
description: summarizeMarkup(tabText(tab), definition.description),
path: tabLink(definition.id, tab.id),
tab,
title: tab.label + TITLE_SEPARATOR + definition.title + TITLE_SEPARATOR + SITE_NAME,
};
return {
banner: subject.banner,
description: subject.description,
kind: OPEN_GRAPH_ARTICLE,
path: subject.path,
robots: ROBOTS_INDEX,
schema: schemaOf(subject),
title: subject.title,
};
};
export const sourceHead = function sourceHead(subject: SourceSubject): PageHead {
return {
alternates: subject.alternates,
banner: shareOf(ANATOMY_PAGE),
description: subject.description,
kind: OPEN_GRAPH_ARTICLE,
path: subject.path,
robots: ROBOTS_INDEX,
schema: sourceSchemaOf(subject),
title: subject.title,
};
};
export const sectionHead = function sectionHead(subject: SectionSubject): PageHead {
return {
alternates: subject.alternates,
banner: shareOf(subject.seed.page),
description: subject.description,
kind: OPEN_GRAPH_ARTICLE,
path: subject.path,
robots: ROBOTS_INDEX,
schema: sectionSchemaOf(subject),
title: subject.title,
};
};
export const recordHead = function recordHead(subject: RecordSubject): PageHead {
return {
alternates: subject.alternates,
banner: shareOf(ONTOLOGY_PAGE),
description: subject.description,
kind: OPEN_GRAPH_ARTICLE,
path: subject.path,
robots: ROBOTS_INDEX,
schema: recordSchemaOf(subject),
title: subject.title,
};
};
export const secondaryTab = function secondaryTab(definition: PageDefinition, pathname: string): Tab | undefined {
if (definition.content.kind !== "tabbed") {
return undefined;
}
const requested = tabFromPath(pathname);
const [first] = definition.content.tabs;
if (requested === null || requested === first?.id) {
return undefined;
}
return definition.content.tabs.find((tab) => tab.id === requested);
};
export const headOf = function headOf(
page: string,
definition?: PageDefinition,
pathname = pagePath(page),
dates?: PageDates,
): PageHead {
if (definition === undefined) {
const subject = {
description: MISSING_PAGE_MESSAGE,
path: pagePath(page),
title: MISSING_PAGE_TITLE + TITLE_SEPARATOR + SITE_NAME,
};
return {
...subject,
banner: shareOf(HOME_PAGE),
kind: OPEN_GRAPH_WEBSITE,
robots: ROBOTS_NOINDEX,
schema: schemaOf(subject),
};
}
const tab = secondaryTab(definition, pathname);
return tab === undefined ? pageHead(definition, dates) : tabHead(definition, tab, dates);
};
const fill = function fill(root: Document, selectors: readonly string[], value: string): void {
for (const selector of selectors) {
root.querySelector(selector)?.setAttribute("content", value);
}
};
export const holdsHead = function holdsHead(root: Document, path: string): boolean {
return root.querySelector(`${CANONICAL_SLOT}[href="${SITE_URL + path}"]`) !== null;
};
export const applyHead = function applyHead(root: Document, head: PageHead): void {
root.title = head.title;
fill(root, TITLE_SLOTS, head.title);
fill(root, DESCRIPTION_SLOTS, head.description);
fill(root, URL_SLOTS, SITE_URL + head.path);
fill(root, IMAGE_SLOTS, SITE_URL + head.banner.source);
fill(root, IMAGE_ALT_SLOTS, head.banner.alt);
fill(root, KIND_SLOTS, head.kind);
fill(root, ROBOTS_SLOTS, head.robots);
root.querySelector(CANONICAL_SLOT)?.setAttribute("href", SITE_URL + head.path);
root.querySelector(NONCE_SLOT)?.setAttribute(CONTENT_ATTRIBUTE, "");
const alternates: readonly (readonly [string, string])[] = [
[MARKDOWN_ALTERNATE_SLOT, head.alternates?.markdown ?? markdownTwinPath(head.path)],
[JSON_ALTERNATE_SLOT, head.alternates?.json ?? payloadTwinPath(head.path)],
];
for (const [selector, path] of alternates) {
const link = root.querySelector(selector);
if (head.robots === ROBOTS_INDEX) {
link?.setAttribute("href", SITE_URL + path);
} else {
link?.removeAttribute("href");
}
}
const slot = root.querySelector(SCHEMA_SLOT);
if (slot !== null) {
slot.textContent = JSON.stringify(head.schema, null, SCHEMA_INDENT);
}
};