# presentation/components/source.component.ts

> 190 lines of code and 38 definitions.

Tree: Site tree
Language: typescript
Layer: product
Canonical: https://banes-lab.com/anatomy/tree#file-presentation-components-source-component-ts
Source text: https://banes-lab.com/assets/sources/source.9aa55b16095ad0d5b410a07e0c082c8560c4f12ef20478411feabb1cdd073218.generated.txt

## Definitions

- `referenceLink` (lexical_declaration, line 61)
- `enableSourceNavigation` (lexical_declaration, line 197, exported)
- `referenceSpan` (lexical_declaration, line 65)
- `hide` (lexical_declaration, line 149)
- `offerCandidates` (lexical_declaration, line 176)
- `markReferences` (lexical_declaration, line 102, exported)
- `markWords` (lexical_declaration, line 82)
- `textNodesOf` (lexical_declaration, line 89)
- `markDocumentReferences` (lexical_declaration, line 133, exported)
- `codeReference` (lexical_declaration, line 116)
- `wordNode` (lexical_declaration, line 73)
- `sharedPanel` (lexical_declaration, line 142)
- `candidateLink` (lexical_declaration, line 166)
- `WORD_CHARS` (lexical_declaration, line 33)
- `NO_CLASS` (lexical_declaration, line 34)
- `unquoted` (lexical_declaration, line 36)
- `trimmed` (lexical_declaration, line 37)
- `first` (lexical_declaration, line 38)
- `last` (lexical_declaration, line 39)
- `wordsOf` (lexical_declaration, line 45)
- `parts` (lexical_declaration, line 46)
- `word` (lexical_declaration, line 48)
- `isWord` (lexical_declaration, line 50)
- `pieces` (lexical_declaration, line 83)
- `found` (lexical_declaration, line 90)
- `walker` (lexical_declaration, line 91)
- `current` (lexical_declaration, line 92)
- `resolved` (lexical_declaration, line 104, exported)
- `file` (lexical_declaration, line 117)
- `folder` (lexical_declaration, line 121)
- `locations` (lexical_declaration, line 125)
- `[only]` (lexical_declaration, line 126)
- `existing` (lexical_declaration, line 143)
- `link` (lexical_declaration, line 167, exported)
- `panel` (lexical_declaration, line 181)
- `ambiguous` (lexical_declaration, line 191)
- `reference` (lexical_declaration, line 199, exported)
- `name` (lexical_declaration, line 203, exported)

## Uses

- [core/factories/element.factory.ts](https://banes-lab.com/source/tree/core/factories/element.factory.ts.md)
- [core/loaders/definition.loader.ts](https://banes-lab.com/source/tree/core/loaders/definition.loader.ts.md)
- [domain/converters/source.converter.ts](https://banes-lab.com/source/tree/domain/converters/source.converter.ts.md)
- [presentation/components/link.component.ts](https://banes-lab.com/source/tree/presentation/components/link.component.ts.md)
- [presentation/components/reference.component.ts](https://banes-lab.com/source/tree/presentation/components/reference.component.ts.md)

## Used by

- [presentation/renderers/markdown.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/markdown.renderer.ts.md)
- [presentation/renderers/source.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/source.renderer.ts.md)

## Source

```typescript
import { DIGITS, LETTERS } from "#configuration/constants/syntax.constants";
import {
    INLINE_CODE_SELECTOR,
    QUOTE_MARKS,
    REFERENCE_CLASS_NAME,
    REFERENCE_NAME_ATTRIBUTE,
    REFERENCE_SELECTOR,
    STRING_TOKEN_SELECTOR,
} from "#configuration/constants/anatomy.constants";
import {
    REFERENCE_CLASS,
    REFERENCE_EDGES_CLASS,
    REFERENCE_RELATION_CLASS,
    REFERENCE_TITLE_CLASS,
} from "#configuration/constants/reference.constants";
import { clearChildren, createElement } from "#core/factories/element.factory";
import {
    definitionsNamed,
    definitionsNamedFrom,
    resolveFolder,
    resolvePath,
    resolveSpecifier,
} from "#core/loaders/definition.loader";
import { folderHref, nodeHref, pathLabel } from "#domain/converters/source.converter";
import type { DefinitionLocation } from "#types/anatomy.types";
import { ESCAPE_KEY } from "#configuration/constants/overlay.constants";
import { LINE_MARK } from "#configuration/strings/folder.strings";
import { OPEN_CLASS } from "#configuration/constants/element.constants";
import { SOURCE_PANEL_ID } from "#core/ids/anatomy.ids";
import { createLink } from "#presentation/components/link.component";
import { placePanel } from "#presentation/components/reference.component";

const WORD_CHARS = LETTERS + DIGITS;
const NO_CLASS = "";

const unquoted = function unquoted(text: string): string {
    const trimmed = text.trim();
    const first = trimmed.charAt(0);
    const last = trimmed.slice(-1);
    return first.length > 0 && QUOTE_MARKS.includes(first) && QUOTE_MARKS.includes(last)
        ? trimmed.slice(1, -1)
        : trimmed;
};

const wordsOf = function wordsOf(text: string): string[] {
    const parts: string[] = [];
    let current = "";
    let word = false;
    for (const char of text) {
        const isWord = WORD_CHARS.includes(char);
        if (current.length > 0 && isWord !== word) {
            parts.push(current);
            current = "";
        }
        current += char;
        word = isWord;
    }
    return current.length === 0 ? parts : [...parts, current];
};

const referenceLink = function referenceLink(text: string, href: string): HTMLElement {
    return createLink(href, REFERENCE_CLASS_NAME, [text]);
};

const referenceSpan = function referenceSpan(text: string, name: string): HTMLElement {
    return createElement("span", {
        attributes: { [REFERENCE_NAME_ATTRIBUTE]: name },
        className: REFERENCE_CLASS_NAME,
        text,
    });
};

const wordNode = function wordNode(part: string, path: string): Node {
    const locations = WORD_CHARS.includes(part.charAt(0)) ? definitionsNamedFrom(part, path) : [];
    const [only] = locations;
    if (locations.length === 1 && only !== undefined) {
        return referenceLink(part, nodeHref(only.file, only.line));
    }
    return locations.length > 1 ? referenceSpan(part, part) : document.createTextNode(part);
};

const markWords = function markWords(node: Text, path: string): void {
    const pieces = wordsOf(node.data).map((part) => wordNode(part, path));
    if (pieces.some((piece) => piece instanceof HTMLElement)) {
        node.replaceWith(...pieces);
    }
};

const textNodesOf = function textNodesOf(code: Element): Text[] {
    const found: Text[] = [];
    const walker = document.createTreeWalker(code, NodeFilter.SHOW_TEXT);
    let current = walker.nextNode();
    while (current !== null) {
        if (current instanceof Text && current.parentElement?.closest(STRING_TOKEN_SELECTOR) === null) {
            found.push(current);
        }
        current = walker.nextNode();
    }
    return found;
};

export const markReferences = function markReferences(code: Element, path: string): void {
    for (const quoted of code.querySelectorAll(STRING_TOKEN_SELECTOR)) {
        const resolved = resolveSpecifier(unquoted(quoted.textContent), path);
        if (resolved !== null) {
            const link = referenceLink(quoted.textContent, nodeHref(resolved, null));
            link.classList.add(...quoted.classList);
            quoted.replaceWith(link);
        }
    }
    for (const node of textNodesOf(code)) {
        markWords(node, path);
    }
};

const codeReference = function codeReference(text: string, path: string): HTMLElement | null {
    const file = resolvePath(text, path) ?? resolveSpecifier(text, path);
    if (file !== null) {
        return referenceLink(text, nodeHref(file, null));
    }
    const folder = resolveFolder(text, path);
    if (folder !== null) {
        return referenceLink(text, folderHref(folder));
    }
    const locations = definitionsNamedFrom(text, path);
    const [only] = locations;
    if (locations.length === 1 && only !== undefined) {
        return referenceLink(text, nodeHref(only.file, only.line));
    }
    return locations.length > 1 ? referenceSpan(text, text) : null;
};

export const markDocumentReferences = function markDocumentReferences(rendered: Element, path: string): void {
    for (const code of rendered.querySelectorAll(INLINE_CODE_SELECTOR)) {
        const reference = codeReference(code.textContent, path);
        if (reference !== null) {
            code.replaceChildren(reference);
        }
    }
};

const sharedPanel = function sharedPanel(): HTMLElement {
    const existing = document.getElementById(SOURCE_PANEL_ID);
    if (existing !== null) {
        return existing;
    }
    const panel = createElement("div", { attributes: { id: SOURCE_PANEL_ID }, className: REFERENCE_CLASS });
    document.body.append(panel);
    const hide = function hide(): void {
        panel.classList.remove(OPEN_CLASS);
        clearChildren(panel);
    };
    document.addEventListener("keydown", (event) => {
        if (event.key === ESCAPE_KEY) {
            hide();
        }
    });
    document.addEventListener("pointerdown", (event) => {
        if (!(event.target instanceof Node) || !panel.contains(event.target)) {
            hide();
        }
    });
    return panel;
};

const candidateLink = function candidateLink(location: DefinitionLocation): HTMLElement {
    const link = createLink(nodeHref(location.file, location.line), NO_CLASS, [
        pathLabel(location.file) + LINE_MARK + String(location.line),
    ]);
    return createElement("div", {
        children: [createElement("span", { children: [link], className: REFERENCE_EDGES_CLASS })],
        className: REFERENCE_RELATION_CLASS,
    });
};

const offerCandidates = function offerCandidates(
    name: string,
    locations: readonly DefinitionLocation[],
    anchor: Element,
): void {
    const panel = sharedPanel();
    clearChildren(panel);
    panel.append(
        createElement("div", { className: REFERENCE_TITLE_CLASS, text: name }),
        ...locations.map(candidateLink),
    );
    panel.classList.add(OPEN_CLASS);
    placePanel(panel, anchor);
};

const ambiguous = function ambiguous(target: EventTarget | null): Element | null {
    const reference = target instanceof Element ? target.closest(REFERENCE_SELECTOR) : null;
    const name = reference?.getAttribute(REFERENCE_NAME_ATTRIBUTE) ?? null;
    return name === null ? null : reference;
};

export const enableSourceNavigation = function enableSourceNavigation(holder: HTMLElement): void {
    holder.addEventListener("click", (event) => {
        const reference = ambiguous(event.target);
        if (reference === null) {
            return;
        }
        const name = reference.getAttribute(REFERENCE_NAME_ATTRIBUTE) ?? "";
        offerCandidates(name, definitionsNamed(name), reference);
    });
};
```
