presentation/components/link.component.ts

presentation/components/link.component.ts is a file in Bane's Lab Site. 87 lines of code and 14 definitions.

import type { ElementChild, ElementSpec } from "#types/element.types";
import { HOME_PATH, pageFromPath, pagePath } from "#core/assets/link.assets";
import { ANCHOR_PREFIX } from "#configuration/constants/document.constants";
import { HOME_PAGE } from "#core/ids/page.ids";
import { ROUTE_REQUESTED } from "#core/ids/route.ids";
import { contextOf } from "#presentation/renderers/element.renderer";
import { createElement } from "#core/factories/element.factory";
import { emitEvent } from "#core/buses/base.bus";
import { getPage } from "#domain/registries/page.registry";
import { isGenericLabel } from "#core/predicates/link.predicate";
import { scrollToId } from "#presentation/components/tab.component";

const specOf = function specOf(
    className: string,
    children: readonly ElementChild[],
    attributes: Readonly<Record<string, string>>,
): ElementSpec {
    return className.length === 0 ? { attributes, children } : { attributes, children, className };
};

const jumpTo = function jumpTo(href: string, targetId: string): void {
    window.history.replaceState({}, "", href);
    scrollToId(targetId);
};

const anchorOf = function anchorOf(href: string): { readonly path: string; readonly target: string | null } {
    const cut = href.indexOf(ANCHOR_PREFIX);
    return cut === -1
        ? { path: href, target: null }
        : { path: href.slice(0, cut), target: href.slice(cut + ANCHOR_PREFIX.length) };
};

const withContext = function withContext(page: string, children: readonly ElementChild[]): readonly ElementChild[] {
    const [only] = children;
    const title = getPage(page)?.title;
    if (children.length !== 1 || typeof only !== "string" || title === undefined || !isGenericLabel(only)) {
        return children;
    }
    return [only, contextOf(title)];
};

export const createPageLink = function createPageLink(
    page: string,
    className: string,
    children: readonly ElementChild[],
    path = pagePath(page),
): HTMLAnchorElement {
    const link = createElement("a", specOf(className, withContext(page, children), { href: path }));
    const anchor = anchorOf(path);
    link.addEventListener("click", (event) => {
        event.preventDefault();
        if (anchor.target !== null && anchor.path === window.location.pathname) {
            jumpTo(path, anchor.target);
            return;
        }
        emitEvent({ name: ROUTE_REQUESTED, page, path });
    });
    return link;
};

const createAnchorLink = function createAnchorLink(
    targetId: string,
    className: string,
    children: readonly ElementChild[],
): HTMLAnchorElement {
    const href = ANCHOR_PREFIX + targetId;
    const link = createElement("a", specOf(className, children, { href }));
    link.addEventListener("click", (event) => {
        event.preventDefault();
        jumpTo(href, targetId);
    });
    return link;
};

export const createExternalLink = function createExternalLink(
    href: string,
    className: string,
    children: readonly ElementChild[],
): HTMLAnchorElement {
    return createElement("a", specOf(className, children, { href, rel: "noopener noreferrer", target: "_blank" }));
};

export const createLink = function createLink(
    href: string,
    className: string,
    children: readonly ElementChild[],
): HTMLAnchorElement {
    if (href.startsWith(ANCHOR_PREFIX)) {
        return createAnchorLink(href.slice(ANCHOR_PREFIX.length), className, children);
    }
    if (!href.startsWith(HOME_PATH)) {
        return createExternalLink(href, className, children);
    }
    return createPageLink(pageFromPath(href, HOME_PAGE), className, children, href);
};