presentation/renderers/glossary.renderer.ts
presentation/renderers/glossary.renderer.ts is a file in Bane's Lab Site. 111 lines of code and 24 definitions.
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("h2", { className: "glossary-title", text: tab.label }));
appendChildren(article, tab.sections.map(renderSection));
return article;
};