presentation/renderers/chapter.renderer.ts
presentation/renderers/chapter.renderer.ts is a file in Bane's Lab Site. 130 lines of code and 27 definitions.
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 { chapterPageLabel } from "#configuration/strings/tab.strings";
import { declareStyle } from "#core/registries/style.registry";
import { fillMarkup } from "#presentation/renderers/text.renderer";
import { nodePath } from "#core/assets/link.assets";
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 });
declareStyle(footer, PANEL_FOOTER_COLUMNS_PROPERTY, String(Math.min(panels.length, PANEL_COLUMN_LIMIT)));
return footer;
};
const labeled = function labeled(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[] = [labeled(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(
labeled(
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("h2", { className: "chapter-title", text: tab.label }));
appendChildren(article, tab.sections.map(renderChapterSection));
return article;
};
const renderSectionEntry = function renderSectionEntry(page: string, tab: Tab, section: Section, index: number): Element {
const prose = createElement("div", { className: "chapter-prose" });
prose.append(
labeled(createElement("h2", { className: "chapter-section-title", text: section.title }), sectionLabel(index)),
);
if (section.intro !== undefined) {
prose.append(fillMarkup(createElement("p", { className: "chapter-text chapter-lead" }), section.intro));
}
const href = nodePath(page, tab.id, section.id);
prose.append(
createElement("p", {
children: [createElement("a", { attributes: { href }, text: chapterPageLabel(section.title) })],
className: "chapter-text chapter-section-link",
}),
);
return createElement("section", { attributes: { id: section.id }, children: [prose], className: "chapter-section" });
};
export const renderChapterIndex = function renderChapterIndex(tab: Tab, page: string): Element {
const article = createElement("article", { className: "chapter" });
article.append(createElement("h2", { className: "chapter-title", text: tab.label }));
appendChildren(
article,
tab.sections.map((section, index) => renderSectionEntry(page, tab, section, index)),
);
return article;
};
export const renderChapterSectionOf = function renderChapterSectionOf(
tab: Tab,
id: string,
rows?: readonly [number, number],
): Element | null {
const index = tab.sections.findIndex((candidate) => candidate.id === id);
const section = tab.sections[index];
if (section === undefined) {
return null;
}
const shown = rows === undefined ? section : { ...section, subsections: section.subsections.slice(...rows) };
return renderChapterSection(shown, index);
};