presentation/renderers/panel.renderer.ts

presentation/renderers/panel.renderer.ts is a file in Bane's Lab Site. 90 lines of code and 19 definitions.

import type { Block, FigureBlock } from "#types/block.types";
import {
    CITE_ATTRIBUTE,
    CITE_CLASS,
    LETTERS,
    MARK_CLASS,
    MARK_SEPARATOR,
    PANEL_ID_INFIX,
    PANEL_TITLE_SELECTORS,
    TAG_CLASS,
    WIDE_PANEL_CLASS,
} from "#configuration/constants/chapter.constants";
import { ANCHOR_PREFIX } from "#configuration/constants/document.constants";
import type { PanelReference } from "#types/panel.types";
import { createElement } from "#core/factories/element.factory";
import { createLink } from "#presentation/components/link.component";
import { renderBlock } from "#presentation/renderers/block.renderer";
import { renderFigure } from "#presentation/renderers/figure.renderer";

const isFigure = function isFigure(block: Block): block is FigureBlock {
    return block.kind === "figure";
};

const isWide = function isWide(block: Block): boolean {
    if (block.kind === "definition") {
        return true;
    }
    return (block.kind === "mermaid" || block.kind === "walk" || block.kind === "metric") && block.wide === true;
};

const panelLetter = function panelLetter(index: number): string {
    const base = LETTERS.length;
    const trailing = LETTERS.charAt(index % base);
    return index < base ? trailing : panelLetter(Math.floor(index / base) - 1) + trailing;
};

const panelMark = function panelMark(sectionLabel: string, index: number): string {
    return sectionLabel + MARK_SEPARATOR + panelLetter(index);
};

const panelId = function panelId(sectionId: string, index: number): string {
    return sectionId + PANEL_ID_INFIX + panelLetter(index);
};

const tagOf = function tagOf(block: Block): string | null {
    if (block.kind === "code" || block.kind === "definition") {
        return block.title;
    }
    if (block.kind === "figure" || block.kind === "mermaid" || block.kind === "surface") {
        return block.caption ?? null;
    }
    return null;
};

const markTitle = function markTitle(panel: Element, mark: string): void {
    for (const selector of PANEL_TITLE_SELECTORS) {
        const title = panel.querySelector(selector);
        if (title !== null) {
            title.classList.add(TAG_CLASS);
            title.prepend(createElement("span", { className: MARK_CLASS, text: mark }));
            return;
        }
    }
};

export const renderChapterPanel = function renderChapterPanel(
    block: Block,
    sectionId: string,
    sectionLabel: string,
    index: number,
): PanelReference {
    const inner = isFigure(block) ? renderFigure(block) : renderBlock(block);
    const mark = panelMark(sectionLabel, index);
    const id = panelId(sectionId, index);
    const wide = isWide(block);
    const element = createElement("div", {
        attributes: { id },
        children: [inner],
        className: wide ? `chapter-panel ${WIDE_PANEL_CLASS}` : "chapter-panel",
    });
    markTitle(element, mark);
    return { element, id, mark, tag: tagOf(block), wide };
};

export const resolveCitations = function resolveCitations(prose: Element, panels: readonly PanelReference[]): void {
    const byTag = new Map(panels.flatMap((panel) => (panel.tag === null ? [] : [[panel.tag, panel] as const])));
    for (const placeholder of [...prose.querySelectorAll(`.${CITE_CLASS}`)]) {
        const panel = byTag.get(placeholder.getAttribute(CITE_ATTRIBUTE) ?? "");
        if (panel === undefined) {
            continue;
        }
        placeholder.replaceWith(
            createLink(ANCHOR_PREFIX + panel.id, CITE_CLASS, [
                createElement("span", { className: MARK_CLASS, text: panel.mark }),
                placeholder.textContent,
            ]),
        );
    }
};