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,
    });
};