presentation/renderers/record.renderer.ts
presentation/renderers/record.renderer.ts is a file in Bane's Lab Site. 129 lines of code and 23 definitions.
import {
ALIASES_LABEL,
DETAILS_LABEL,
KIND_LABEL,
LABEL_SEPARATOR,
LAYER_LABEL,
LIST_SEPARATOR,
recordLinkText,
} from "#configuration/strings/ontology.strings";
import { RECORD_CLASS, SEARCH_ATTRIBUTE } from "#configuration/constants/ontology.constants";
import type { RecordGroup, RecordLink, RecordPlacement, RecordSubject } from "#types/page.types";
import { appendChildren, createElement } from "#core/factories/element.factory";
import { fillMarkup, plainText } from "#presentation/renderers/text.renderer";
import type { ElementChild } from "#types/element.types";
import type { RowIdentity } from "#types/ontology.types";
import { SPACE } from "#configuration/constants/document.constants";
import { TOGGLE_EVENT } from "#configuration/constants/element.constants";
import { anchorMarkup } from "#core/formatters/link.formatter";
import { escaped } from "#core/normalizers/markdown.normalizer";
import { isGenericLabel } from "#core/predicates/link.predicate";
import { isStaticRender } from "#core/predicates/document.predicate";
import { listedInSentence } from "#configuration/strings/catalog.strings";
import { recordPageOf } from "#domain/converters/ontology.converter";
import { relationLabelOf } from "#configuration/strings/reference.strings";
const linkOf = function linkOf(link: RecordLink): ElementChild {
return link.path === null ? link.label : createElement("a", { attributes: { href: link.path }, text: link.label });
};
const fieldOf = function fieldOf(label: string, value: readonly ElementChild[]): Element {
return createElement("p", { children: [label + LABEL_SEPARATOR, ...value], className: "record-field" });
};
const groupOf = function groupOf(group: RecordGroup): readonly Element[] {
return [
createElement("h2", { text: relationLabelOf(group.relation) }),
createElement("ul", { children: group.links.map((link) => createElement("li", { children: [linkOf(link)] })) }),
];
};
const markupOf = function markupOf(link: RecordLink | null): string | null {
if (link === null) {
return null;
}
return anchorMarkup(link.path, escaped(link.label));
};
const placementOf = function placementOf(placement: RecordPlacement | null): readonly Element[] {
if (placement === null) {
return [];
}
const sentence = listedInSentence(
markupOf(placement.up) ?? "",
markupOf(placement.previous),
markupOf(placement.next),
);
return [fillMarkup(createElement("p", { className: "record-placement" }), sentence)];
};
export const renderRecordBody = function renderRecordBody(subject: RecordSubject): Element {
const layer = subject.layer === null ? [] : [fieldOf(LAYER_LABEL, [linkOf(subject.layer)])];
const aliases = subject.aliases.length === 0 ? [] : [fieldOf(ALIASES_LABEL, [subject.aliases.join(LIST_SEPARATOR)])];
return createElement("article", {
children: [
createElement("nav", { children: [linkOf(subject.collection)] }),
createElement("h1", { text: subject.name }),
...(subject.code === null ? [] : [createElement("p", { children: [createElement("code", { text: subject.code })] })]),
fieldOf(KIND_LABEL, [subject.kind]),
...layer,
...aliases,
...subject.facts.map((fact) => fieldOf(fact.label, [linkOf(fact.value)])),
...(subject.summary === null ? [] : [createElement("p", { text: subject.summary })]),
...placementOf(subject.placement),
...subject.relations.filter((group) => group.links.length > 0).flatMap(groupOf),
],
className: "record-page",
});
};
const haystack = function haystack(parts: readonly string[]): string {
return parts.map(plainText).join(SPACE).toLowerCase();
};
const renderChips = function renderChips(items: readonly string[]): Element {
const list = createElement("ul", { className: "record-chips" });
appendChildren(
list,
items.map((item) => fillMarkup(createElement("li", { className: "record-chip" }), item)),
);
return list;
};
export const renderDetails = function renderDetails(label: string, build: () => readonly Element[]): Element {
const details = createElement("details", {
children: [createElement("summary", { className: "record-summary", text: label })],
className: "record-details",
});
if (isStaticRender()) {
appendChildren(details, build());
return details;
}
details.addEventListener(
TOGGLE_EVENT,
() => {
appendChildren(details, build());
},
{ once: true },
);
return details;
};
const pageLink = function pageLink(page: string, row: RowIdentity): Element {
const text = isGenericLabel(row.title) ? recordLinkText(row.title, row.context) : row.title;
return createElement("a", { attributes: { href: page }, text });
};
export const renderRecordRow = function renderRecordRow(
row: RowIdentity,
chips: readonly string[],
body: (() => readonly Element[]) | null,
searchable: readonly string[],
): Element {
const { id, title } = row;
const page = isStaticRender() ? recordPageOf(id) : null;
const name = page === null ? title : pageLink(page, row);
const header = createElement("header", {
children: [
createElement("h3", { children: [name], className: "record-title" }),
...(chips.length === 0 ? [] : [renderChips(chips)]),
],
className: "record-header",
});
const children = body === null || page !== null ? [header] : [header, renderDetails(DETAILS_LABEL, body)];
const search = isStaticRender() ? {} : { [SEARCH_ATTRIBUTE]: haystack([title, ...chips, ...searchable]) };
return createElement("article", {
attributes: { id, ...search },
children,
className: RECORD_CLASS,
});
};