import { PAIRED_SECTION_CLASS, PANEL_COLUMN_CLASS, PANEL_COLUMN_LIMIT, PANEL_FOOTER_CLASS, PANEL_FOOTER_COLUMNS_PROPERTY, PANEL_KINDS, } from "#configuration/constants/chapter.constants"; import type { Section, Subsection, Tab } from "#types/document.types"; import { appendChildren, createElement } from "#core/factories/element.factory"; import { renderChapterPanel, resolveCitations } from "#presentation/renderers/panel.renderer"; import { sectionLabel, subsectionId, subsectionLabel } from "#presentation/renderers/section.renderer"; import type { Block } from "#types/block.types"; import type { PanelReference } from "#types/panel.types"; import { fillMarkup } from "#presentation/renderers/text.renderer"; import { renderBlock } from "#presentation/renderers/block.renderer"; const isPanel = function isPanel(block: Block): boolean { return PANEL_KINDS.has(block.kind); }; const renderProseBlocks = function renderProseBlocks(blocks: readonly Block[]): Element[] { return blocks.filter((block) => !isPanel(block)).map(renderBlock); }; const panelBlocksOf = function panelBlocksOf(section: Section): Block[] { return [...(section.blocks ?? []), ...section.subsections.flatMap((sub) => sub.blocks ?? [])].filter(isPanel); }; const renderPanels = function renderPanels(section: Section, label: string): PanelReference[] { return panelBlocksOf(section).map((block, index) => renderChapterPanel(block, section.id, label, index)); }; const renderFooter = function renderFooter(panels: readonly Element[]): HTMLDivElement { const footer = createElement("div", { children: panels, className: PANEL_FOOTER_CLASS }); footer.style.setProperty(PANEL_FOOTER_COLUMNS_PROPERTY, String(Math.min(panels.length, PANEL_COLUMN_LIMIT))); return footer; }; const labelled = function labelled(heading: Element, label: string): Element { heading.prepend(createElement("span", { className: "subsection-symbol chapter-label", text: label })); return heading; }; const renderSubsection = function renderSubsection(sub: Subsection, id: string, label: string): Element[] { const heading = createElement("h3", { attributes: { id }, className: "chapter-subsection-title", text: sub.title }); const held: Element[] = [labelled(heading, label)]; if (sub.content !== undefined) { held.push(fillMarkup(createElement("p", { className: "chapter-text" }), sub.content)); } return [...held, ...renderProseBlocks(sub.blocks ?? [])]; }; export const renderChapterSection = function renderChapterSection(section: Section, sectionIndex: number): Element { const prose = createElement("div", { className: "chapter-prose" }); prose.append( labelled( createElement("h2", { className: "chapter-section-title", text: section.title }), sectionLabel(sectionIndex), ), ); if (section.intro !== undefined) { prose.append(fillMarkup(createElement("p", { className: "chapter-text chapter-lead" }), section.intro)); } appendChildren(prose, renderProseBlocks(section.blocks ?? [])); appendChildren( prose, section.subsections.flatMap((sub, index) => renderSubsection(sub, subsectionId(section.id, index, sub.id), subsectionLabel(sectionIndex, index)), ), ); const panels = renderPanels(section, sectionLabel(sectionIndex)); resolveCitations(prose, panels); const narrow = panels.filter((panel) => !panel.wide).map((panel) => panel.element); const wide = panels.filter((panel) => panel.wide).map((panel) => panel.element); const beside = narrow.slice(0, PANEL_COLUMN_LIMIT); const below = [...wide, ...narrow.slice(PANEL_COLUMN_LIMIT)]; const children = [prose]; if (beside.length > 0) { children.push(createElement("div", { children: beside, className: PANEL_COLUMN_CLASS })); } if (below.length > 0) { children.push(renderFooter(below)); } return createElement("section", { attributes: { id: section.id }, children, className: beside.length > 0 ? `chapter-section ${PAIRED_SECTION_CLASS}` : "chapter-section", }); }; export const renderChapter = function renderChapter(tab: Tab): Element { const article = createElement("article", { className: "chapter" }); article.append(createElement("h1", { className: "chapter-title", text: tab.label })); appendChildren(article, tab.sections.map(renderChapterSection)); return article; };