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