# presentation/renderers/glossary.renderer.ts

> 111 lines of code and 24 definitions.

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

## Definitions

- `lettersOf` (lexical_declaration, line 28, exported)
- `letterId` (lexical_declaration, line 20)
- `renderGlossarySection` (lexical_declaration, line 102)
- `renderLetterGroup` (lexical_declaration, line 67)
- `renderTerm` (lexical_declaration, line 38)
- `renderLetterJumps` (lexical_declaration, line 77, exported)
- `renderEntries` (lexical_declaration, line 84)
- `renderName` (lexical_declaration, line 32)
- `renderGlossaryTab` (lexical_declaration, line 122, exported)
- `isGlossary` (lexical_declaration, line 12)
- `letterOf` (lexical_declaration, line 16)
- `sortedEntries` (lexical_declaration, line 24, exported)
- `holdsGlossary` (lexical_declaration, line 98)
- `renderEntry` (lexical_declaration, line 60)
- `renderGlossaryBlock` (lexical_declaration, line 94)
- `renderSection` (lexical_declaration, line 116)
- `heading` (lexical_declaration, line 39)
- `term` (lexical_declaration, line 44)
- `list` (lexical_declaration, line 68)
- `entries` (lexical_declaration, line 78, exported)
- `sorted` (lexical_declaration, line 85)
- `element` (lexical_declaration, line 103)
- `title` (lexical_declaration, line 104)
- `article` (lexical_declaration, line 123, exported)

## Uses

- [core/factories/element.factory.ts](https://banes-lab.com/source/tree/core/factories/element.factory.ts.md)
- [presentation/components/link.component.ts](https://banes-lab.com/source/tree/presentation/components/link.component.ts.md)
- [presentation/components/tab.component.ts](https://banes-lab.com/source/tree/presentation/components/tab.component.ts.md)
- [presentation/renderers/block.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/block.renderer.ts.md)
- [presentation/renderers/chapter.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/chapter.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/tab.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/tab.renderer.ts.md)

## Source

```typescript
import type { Block, GlossaryBlock, GlossaryEntry } from "#types/block.types";
import { GLOSSARY_LETTER_PREFIX, GLOSSARY_LETTER_SEPARATOR } from "#configuration/constants/glossary.constants";
import type { Section, Tab } from "#types/document.types";
import { appendChildren, createElement } from "#core/factories/element.factory";
import { createJumpButton } from "#presentation/components/tab.component";
import { createLink } from "#presentation/components/link.component";
import { fillMarkup } from "#presentation/renderers/text.renderer";
import { renderBlock } from "#presentation/renderers/block.renderer";
import { renderChapterSection } from "#presentation/renderers/chapter.renderer";
import { sectionLabel } from "#presentation/renderers/section.renderer";

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

const letterOf = function letterOf(entry: GlossaryEntry): string {
    return entry.term.charAt(0).toUpperCase();
};

const letterId = function letterId(letter: string): string {
    return GLOSSARY_LETTER_PREFIX + letter;
};

export const sortedEntries = function sortedEntries(entries: readonly GlossaryEntry[]): readonly GlossaryEntry[] {
    return [...entries].sort((a, b) => a.term.localeCompare(b.term));
};

export const lettersOf = function lettersOf(entries: readonly GlossaryEntry[]): readonly string[] {
    return [...new Set(sortedEntries(entries).map(letterOf))];
};

const renderName = function renderName(entry: GlossaryEntry): Element {
    return entry.href === undefined
        ? createElement("span", { className: "glossary-name", text: entry.term })
        : createLink(entry.href, "glossary-name", [entry.term]);
};

const renderTerm = function renderTerm(entry: GlossaryEntry): Element {
    const heading = createElement("span", { className: "glossary-heading" });
    heading.append(renderName(entry));
    if (entry.code !== undefined) {
        heading.append(createElement("span", { className: "glossary-code", text: entry.code }));
    }
    const term = createElement("dt", {
        attributes: entry.id === undefined ? {} : { id: entry.id },
        children: [heading],
        className: "glossary-term",
    });
    if (entry.domains !== undefined) {
        term.append(
            createElement("span", {
                className: "glossary-domains",
                text: entry.domains.join(GLOSSARY_LETTER_SEPARATOR),
            }),
        );
    }
    return term;
};

const renderEntry = function renderEntry(entry: GlossaryEntry): readonly Element[] {
    return [
        renderTerm(entry),
        fillMarkup(createElement("dd", { className: "glossary-definition" }), entry.description),
    ];
};

const renderLetterGroup = function renderLetterGroup(letter: string, entries: readonly GlossaryEntry[]): Element {
    const list = createElement("dl", { className: "glossary-list" });
    appendChildren(list, entries.flatMap(renderEntry));
    return createElement("section", {
        attributes: { id: letterId(letter) },
        children: [createElement("h2", { className: "glossary-letter", text: letter }), list],
        className: "glossary-group",
    });
};

export const renderLetterJumps = function renderLetterJumps(tab: Tab): readonly Element[] {
    const entries = tab.sections
        .flatMap((section) => (section.blocks ?? []).filter(isGlossary))
        .flatMap((block) => block.entries);
    return lettersOf(entries).map((letter) => createJumpButton(letter, letter, letterId(letter)));
};

const renderEntries = function renderEntries(block: GlossaryBlock): readonly Element[] {
    const sorted = sortedEntries(block.entries);
    return lettersOf(sorted).map((letter) =>
        renderLetterGroup(
            letter,
            sorted.filter((entry) => letterOf(entry) === letter),
        ),
    );
};

const renderGlossaryBlock = function renderGlossaryBlock(block: Block): readonly Element[] {
    return isGlossary(block) ? renderEntries(block) : [renderBlock(block)];
};

const holdsGlossary = function holdsGlossary(section: Section): boolean {
    return (section.blocks ?? []).some(isGlossary);
};

const renderGlossarySection = function renderGlossarySection(section: Section, sectionIndex: number): Element {
    const element = createElement("section", { attributes: { id: section.id }, className: "glossary-section" });
    const title = createElement("h2", { className: "glossary-section-title", text: section.title });
    title.prepend(
        createElement("span", { className: "subsection-symbol chapter-label", text: sectionLabel(sectionIndex) }),
    );
    element.append(title);
    if (section.intro !== undefined) {
        element.append(fillMarkup(createElement("p", { className: "glossary-intro" }), section.intro));
    }
    appendChildren(element, (section.blocks ?? []).flatMap(renderGlossaryBlock));
    return element;
};

const renderSection = function renderSection(section: Section, sectionIndex: number): Element {
    return holdsGlossary(section)
        ? renderGlossarySection(section, sectionIndex)
        : renderChapterSection(section, sectionIndex);
};

export const renderGlossaryTab = function renderGlossaryTab(tab: Tab): Element {
    const article = createElement("article", { className: "glossary" });
    article.append(createElement("h1", { className: "glossary-title", text: tab.label }));
    appendChildren(article, tab.sections.map(renderSection));
    return article;
};
```
