# presentation/renderers/ontology.renderer.ts

> 193 lines of code and 46 definitions.

Tree: Site tree
Language: typescript
Layer: product
Canonical: https://banes-lab.com/anatomy/tree#file-presentation-renderers-ontology-renderer-ts
Source text: https://banes-lab.com/assets/sources/source.cf9dfd25dc443e4d79e1374a86210ed3ed180b1b3d972460a7cce9b33c1560a2.generated.txt

## Definitions

- `renderRecord` (lexical_declaration, line 74)
- `renderFields` (lexical_declaration, line 107, exported)
- `isGlossary` (lexical_declaration, line 32)
- `renderDetails` (lexical_declaration, line 55)
- `renderExplorer` (lexical_declaration, line 199, exported)
- `entryRecord` (lexical_declaration, line 96)
- `renderDiagrams` (lexical_declaration, line 143)
- `isDiagram` (lexical_declaration, line 38)
- `haystack` (lexical_declaration, line 42)
- `applyQuery` (lexical_declaration, line 184)
- `renderGroup` (lexical_declaration, line 153)
- `subsectionRecords` (lexical_declaration, line 124)
- `renderNav` (lexical_declaration, line 166)
- `renderChips` (lexical_declaration, line 46)
- `renderBody` (lexical_declaration, line 120)
- `panels` (lexical_declaration, line 148)
- `SPACE` (lexical_declaration, line 21)
- `RECORD_CLASS` (lexical_declaration, line 22)
- `GROUP_CLASS` (lexical_declaration, line 23)
- `FIELD_CLASS` (lexical_declaration, line 24)
- `WIDE_FIELD_CLASS` (lexical_declaration, line 25)
- `TOGGLE_EVENT` (lexical_declaration, line 26)
- `isList` (lexical_declaration, line 28)
- `DIAGRAM_KINDS` (lexical_declaration, line 36)
- `details` (lexical_declaration, line 56)
- `header` (lexical_declaration, line 81)
- `children` (lexical_declaration, line 88)
- `fieldClass` (lexical_declaration, line 103)
- `cards` (lexical_declaration, line 108, exported)
- `blocks` (lexical_declaration, line 129)
- `[first]` (lexical_declaration, line 130)
- `chips` (lexical_declaration, line 134)
- `rest` (lexical_declaration, line 135)
- `searchable` (lexical_declaration, line 136)
- `diagrams` (lexical_declaration, line 144)
- `group` (lexical_declaration, line 155)
- `list` (lexical_declaration, line 167)
- `records` (lexical_declaration, line 185)
- `shown` (lexical_declaration, line 186)
- `visible` (lexical_declaration, line 188)
- `held` (lexical_declaration, line 193)
- `groups` (lexical_declaration, line 200, exported)
- `count` (lexical_declaration, line 201, exported)
- `search` (lexical_declaration, line 202, exported)
- `toolbar` (lexical_declaration, line 205, exported)
- `body` (lexical_declaration, line 206, exported)

## Uses

- [core/factories/element.factory.ts](https://banes-lab.com/source/tree/core/factories/element.factory.ts.md)
- [core/predicates/document.predicate.ts](https://banes-lab.com/source/tree/core/predicates/document.predicate.ts.md)
- [presentation/renderers/block.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/block.renderer.ts.md)
- [presentation/renderers/section.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/section.renderer.ts.md)
- [presentation/renderers/text.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/text.renderer.ts.md)

## Used by

- [presentation/renderers/folder.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/folder.renderer.ts.md)
- [presentation/renderers/tab.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/tab.renderer.ts.md)

## Source

```typescript
import type { Block, GlossaryBlock, GlossaryEntry, ListBlock } from "#types/block.types";
import {
    CATEGORIES_LABEL,
    DETAILS_LABEL,
    DIAGRAM_LABEL,
    OF_LABEL,
    SEARCH_LABEL,
    SEARCH_PLACEHOLDER,
    SHOWN_LABEL,
} from "#configuration/strings/ontology.strings";
import { SEARCH_ATTRIBUTE, WIDE_FIELD_LENGTH } from "#configuration/constants/ontology.constants";
import type { Section, Subsection, Tab } from "#types/document.types";
import { appendChildren, createElement } from "#core/factories/element.factory";
import { fillMarkup, plainText } from "#presentation/renderers/text.renderer";
import { ANCHOR_PREFIX } from "#configuration/constants/document.constants";
import { createFilterBox } from "#presentation/components/filter.component";
import { isStaticRender } from "#core/predicates/document.predicate";
import { renderBlock } from "#presentation/renderers/block.renderer";
import { subsectionId } from "#presentation/renderers/section.renderer";

const SPACE = " ";
const RECORD_CLASS = "record";
const GROUP_CLASS = "explorer-group";
const FIELD_CLASS = "record-field";
const WIDE_FIELD_CLASS = "record-field-wide";
const TOGGLE_EVENT = "toggle";

const isList = function isList(block: Block): block is ListBlock {
    return block.kind === "list";
};

const isGlossary = function isGlossary(block: Block): block is GlossaryBlock {
    return block.kind === "glossary";
};

const DIAGRAM_KINDS: ReadonlySet<Block["kind"]> = new Set<Block["kind"]>(["mermaid", "walk"]);

const isDiagram = function isDiagram(block: Block): boolean {
    return DIAGRAM_KINDS.has(block.kind);
};

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

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 renderRecord = function renderRecord(
    id: string,
    title: string,
    chips: readonly string[],
    body: (() => readonly Element[]) | null,
    searchable: readonly string[],
): Element {
    const header = createElement("header", {
        children: [
            createElement("h3", { className: "record-title", text: title }),
            ...(chips.length === 0 ? [] : [renderChips(chips)]),
        ],
        className: "record-header",
    });
    const children = body === null ? [header] : [header, renderDetails(DETAILS_LABEL, body)];
    return createElement("article", {
        attributes: { id, [SEARCH_ATTRIBUTE]: haystack([title, ...chips, ...searchable]) },
        children,
        className: RECORD_CLASS,
    });
};

const entryRecord = function entryRecord(entry: GlossaryEntry, index: number, prefix: string): Element {
    const body = (): readonly Element[] => [
        fillMarkup(createElement("p", { className: "record-text" }), entry.description),
    ];
    return renderRecord(entry.id ?? subsectionId(prefix, index), entry.term, [], body, [entry.description]);
};

const fieldClass = function fieldClass(entry: GlossaryEntry): string {
    return plainText(entry.description).length > WIDE_FIELD_LENGTH ? `${FIELD_CLASS} ${WIDE_FIELD_CLASS}` : FIELD_CLASS;
};

export const renderFields = function renderFields(block: GlossaryBlock): Element {
    const cards = block.entries.map((entry) =>
        createElement("div", {
            children: [
                createElement("span", { className: "record-field-label", text: entry.term }),
                fillMarkup(createElement("div", { className: "record-field-value" }), entry.description),
            ],
            className: fieldClass(entry),
        }),
    );
    return createElement("div", { children: cards, className: "record-fields" });
};

const renderBody = function renderBody(block: Block): Element {
    return isGlossary(block) ? renderFields(block) : renderBlock(block);
};

const subsectionRecords = function subsectionRecords(
    sub: Subsection,
    sectionId: string,
    index: number,
): readonly Element[] {
    const blocks = sub.blocks ?? [];
    const [first] = blocks;
    if (sub.id === undefined && blocks.length === 1 && first !== undefined && isGlossary(first)) {
        return first.entries.map((entry, at) => entryRecord(entry, at, subsectionId(sectionId, index)));
    }
    const chips = first !== undefined && isList(first) ? first.items : [];
    const rest = blocks.filter((block) => block !== first || !isList(block));
    const searchable = blocks.flatMap((block) =>
        isGlossary(block) ? block.entries.map((entry) => entry.term + SPACE + entry.description) : [],
    );
    const body = rest.length === 0 ? null : (): readonly Element[] => rest.map(renderBody);
    return [renderRecord(subsectionId(sectionId, index, sub.id), sub.title, chips, body, searchable)];
};

const renderDiagrams = function renderDiagrams(blocks: readonly Block[]): readonly Element[] {
    const diagrams = blocks.filter(isDiagram);
    if (diagrams.length === 0) {
        return [];
    }
    const panels = (): readonly Element[] =>
        diagrams.map((block) => createElement("div", { children: [renderBlock(block)], className: "chapter-panel" }));
    return [renderDetails(DIAGRAM_LABEL, panels)];
};

const renderGroup = function renderGroup(section: Section): Element {
    const records = section.subsections.flatMap((sub, index) => subsectionRecords(sub, section.id, index));
    const group = createElement("section", { attributes: { id: section.id }, className: GROUP_CLASS });
    group.append(createElement("h2", { className: "explorer-group-title", text: section.title }));
    if (section.intro !== undefined) {
        group.append(fillMarkup(createElement("p", { className: "explorer-intro" }), section.intro));
    }
    appendChildren(group, (section.blocks ?? []).filter((block) => !isDiagram(block)).map(renderBlock));
    appendChildren(group, renderDiagrams(section.blocks ?? []));
    group.append(createElement("div", { children: records, className: "explorer-records" }));
    return group;
};

const renderNav = function renderNav(sections: readonly Section[]): Element {
    const list = createElement("ul", { className: "explorer-nav-list" });
    appendChildren(
        list,
        sections.map((section) =>
            createElement("li", {
                children: [
                    createElement("a", { attributes: { href: ANCHOR_PREFIX + section.id }, text: section.title }),
                ],
            }),
        ),
    );
    return createElement("nav", {
        children: [createElement("h2", { className: "explorer-nav-title", text: CATEGORIES_LABEL }), list],
        className: "explorer-nav",
    });
};

const applyQuery = function applyQuery(root: Element, count: Element, query: string): void {
    const records = [...root.querySelectorAll<HTMLElement>(`.${RECORD_CLASS}`)];
    let shown = 0;
    for (const record of records) {
        const visible = query.length === 0 || (record.getAttribute(SEARCH_ATTRIBUTE) ?? "").includes(query);
        record.hidden = !visible;
        shown += visible ? 1 : 0;
    }
    for (const group of root.querySelectorAll<HTMLElement>(`.${GROUP_CLASS}`)) {
        const held = group.querySelector(`.${RECORD_CLASS}`) !== null;
        group.hidden = held && group.querySelector(`.${RECORD_CLASS}:not([hidden])`) === null;
    }
    count.textContent = [String(shown), OF_LABEL, String(records.length), SHOWN_LABEL].join(SPACE);
};

export const renderExplorer = function renderExplorer(tab: Tab): Element {
    const groups = createElement("div", { children: tab.sections.map(renderGroup), className: "explorer-groups" });
    const count = createElement("span", { className: "explorer-count" });
    const search = createFilterBox(SEARCH_PLACEHOLDER, SEARCH_LABEL, (query) => {
        applyQuery(groups, count, query);
    });
    const toolbar = createElement("div", { children: [search, count], className: "explorer-toolbar" });
    const body = createElement("div", { children: [renderNav(tab.sections), groups], className: "explorer-body" });
    applyQuery(groups, count, "");
    return createElement("article", {
        children: [createElement("h1", { className: "explorer-title", text: tab.label }), toolbar, body],
        className: "explorer",
    });
};
```
