presentation/renderers/section.renderer.ts

presentation/renderers/section.renderer.ts is a file in Bane's Lab Site. 92 lines of code and 23 definitions.

import { FADE_IN, FADE_IN_UP } from "#configuration/constants/element.constants";
import {
    LABEL_GROUP_SIZE,
    LABEL_SEPARATOR,
    SECTION_LETTERS,
    SUBSECTION_INFIX,
} from "#configuration/constants/tab.constants";
import type { Section, Subsection } from "#types/document.types";
import { appendChildren, createElement } from "#core/factories/element.factory";
import { COPY_SECTION_TITLE } from "#configuration/strings/tab.strings";
import { ICON_BASE_CLASS } from "#configuration/icons/element.icons";
import { IGNORED_SELECTORS } from "#configuration/constants/document.constants";
import { animated } from "#presentation/renderers/element.renderer";
import { convertText } from "#core/converters/text.converter";
import { createCopyButton } from "#presentation/components/clipboard.component";
import { fillMarkup } from "#presentation/renderers/text.renderer";
import { renderBlock } from "#presentation/renderers/block.renderer";

const CALLOUT = "callout";
const GRID = "grid";
const DELAY_INTRO = 50;

export const subsectionId = function subsectionId(section: string, index: number, own?: string): string {
    return own ?? section + SUBSECTION_INFIX + String(index);
};

const sectionLetter = function sectionLetter(sectionIndex: number): string {
    const base = SECTION_LETTERS.length;
    const last = SECTION_LETTERS.charAt(sectionIndex % base);
    const rest = Math.floor(sectionIndex / base);
    return rest === 0 ? last : sectionLetter(rest - 1) + last;
};

export const sectionLabel = function sectionLabel(sectionIndex: number): string {
    return sectionLetter(sectionIndex) + String(1);
};

export const subsectionLabel = function subsectionLabel(sectionIndex: number, subIndex: number): string {
    const group = Math.floor(subIndex / LABEL_GROUP_SIZE) + 1;
    const position = (subIndex % LABEL_GROUP_SIZE) + 1;
    return sectionLetter(sectionIndex) + String(group) + LABEL_SEPARATOR + String(position);
};

const renderSubsectionTitle = function renderSubsectionTitle(sub: Subsection, label: string | undefined): Element {
    const title = createElement("h3", { className: "subsection-title", text: sub.title });
    if (label !== undefined) {
        title.append(createElement("span", { className: "subsection-symbol", text: label }));
    }
    return title;
};

const renderSubsection = function renderSubsection(sub: Subsection, id: string, label: string | undefined): Element {
    const wrapper = animated("div", { attributes: { id }, className: "tab-subsection" }, FADE_IN_UP);
    wrapper.append(renderSubsectionTitle(sub, label));
    if (sub.content !== undefined) {
        wrapper.append(fillMarkup(createElement("p", { className: "section-text" }), sub.content));
    }
    const blocks = (sub.blocks ?? []).map(renderBlock);
    if (sub.layout === GRID) {
        wrapper.append(createElement("div", { children: blocks, className: "tab-subsection-grid" }));
        return wrapper;
    }
    appendChildren(wrapper, blocks);
    return wrapper;
};

const renderTitle = function renderTitle(section: Section, target: Element, label: string | undefined): Element {
    const wrapper = createElement("div", { className: "section-title-wrapper" });
    if (section.layout === CALLOUT) {
        wrapper.append(createElement("i", { className: `${ICON_BASE_CLASS} ${section.icon} callout-icon` }));
    }
    const title = animated("h2", { className: "section-title", text: section.title }, FADE_IN_UP);
    if (label !== undefined) {
        title.append(createElement("span", { className: "subsection-symbol", text: label }));
    }
    appendChildren(wrapper, [
        title,
        createCopyButton(() => convertText(target, IGNORED_SELECTORS), "copy-button medium", COPY_SECTION_TITLE),
    ]);
    return wrapper;
};

export const renderTabSection = function renderTabSection(section: Section, sectionIndex: number): Element {
    const callout = section.layout === CALLOUT;
    const className = callout ? "tab-section callout" : "tab-section";
    const element = createElement("section", { attributes: { id: section.id }, className });
    element.append(renderTitle(section, element, callout ? undefined : sectionLabel(sectionIndex)));
    if (section.intro !== undefined) {
        element.append(fillMarkup(animated("p", { className: "section-text" }, FADE_IN, DELAY_INTRO), section.intro));
    }
    appendChildren(element, (section.blocks ?? []).map(renderBlock));
    const subsections = section.subsections.map((sub, index) =>
        renderSubsection(
            sub,
            subsectionId(section.id, index, sub.id),
            callout ? undefined : subsectionLabel(sectionIndex, index),
        ),
    );
    element.append(createElement("div", { children: subsections, className: "tab-section-grid" }));
    return element;
};