domain/converters/search.converter.ts

domain/converters/search.converter.ts is a file in Bane's Lab Site. 125 lines of code and 19 definitions.

import { ANATOMY_TITLE, TITLE_SEPARATOR } from "#configuration/strings/page.strings";
import { DEFINITION_MIN_TERM_LENGTH, DEFINITION_RESULT_LIMIT } from "#configuration/constants/search.constants";
import type { SearchCorpus, SearchHit, SearchIndex, SearchSource, SearchSpread } from "#types/search.types";
import { UNPLACED, anchorPath } from "#core/analyzers/search.analyzer";
import { indexOf, positionOf } from "#domain/converters/search.index.converter";
import { nodeHref, pathLabel } from "#domain/converters/source.converter";
import { ANATOMY_ICON } from "#configuration/icons/page.icons";
import { ANATOMY_PAGE } from "#core/ids/page.ids";
import { ANATOMY_TONE } from "#configuration/constants/tab.constants";
import type { DefinitionLocation } from "#types/anatomy.types";
import { SEARCH_DEFINITIONS_ICON } from "#configuration/icons/search.icons";
import { SEARCH_DEFINITIONS_SECTION_ID } from "#core/ids/search.ids";
import { SEARCH_DEFINITIONS_TITLE } from "#configuration/strings/search.strings";
import type { Section } from "#types/document.types";
import { TREE_TAB } from "#core/ids/anatomy.ids";
import { narrowSection } from "#domain/converters/search.fragment.converter";
import { queryTerms } from "#core/matchers/search.matcher";

const ID_JOIN = "-";

interface OpenSpread {
    readonly home: string;
    readonly sections: Section[];
    readonly source: SearchSource;
}

const DEFINITION_SOURCE: SearchSource = {
    first: false,
    icon: ANATOMY_ICON,
    label: ANATOMY_TITLE + TITLE_SEPARATOR + SEARCH_DEFINITIONS_TITLE,
    layout: "chapter",
    page: ANATOMY_PAGE,
    tab: { icon: ANATOMY_ICON, id: TREE_TAB, label: SEARCH_DEFINITIONS_TITLE, sections: [] },
    tone: ANATOMY_TONE,
};

const sectionHits = function sectionHits(index: SearchIndex, terms: readonly string[]): readonly SearchHit[] {
    return index.sources.flatMap((source, sourceIndex) =>
        source.tab.sections.flatMap((section, sectionIndex) => {
            const narrowed = narrowSection(terms, section);
            if (narrowed === null) {
                return [];
            }
            const home = anchorPath(source, section.id);
            const position = positionOf(index, source, narrowed);
            return [{ home, position, section: narrowed, sectionIndex, source, sourceIndex }];
        }),
    );
};

export const definitionMatches = function definitionMatches(
    terms: readonly string[],
    location: DefinitionLocation,
): boolean {
    const name = location.name.toLowerCase();
    return terms.every((term) => term.length >= DEFINITION_MIN_TERM_LENGTH && name.includes(term));
};

const definitionSection = function definitionSection(location: DefinitionLocation, at: number): Section {
    return {
        icon: SEARCH_DEFINITIONS_ICON,
        id: SEARCH_DEFINITIONS_SECTION_ID + ID_JOIN + String(at),
        subsections: [
            {
                blocks: [
                    {
                        file: location.file,
                        kind: "definition",
                        line: location.line,
                        name: location.name,
                        title: pathLabel(location.file),
                    },
                ],
                title: pathLabel(location.file),
            },
        ],
        title: location.name,
    };
};

const byPlace = function byPlace(left: number, right: number): number {
    return left === right ? 0 : left - right;
};

const definitionHits = function definitionHits(
    index: SearchIndex,
    corpus: SearchCorpus,
    terms: readonly string[],
): readonly SearchHit[] {
    return corpus.definitions
        .filter((location) => definitionMatches(terms, location))
        .map((location) => ({ location, position: index.evidence.get(location.name) ?? UNPLACED }))
        .toSorted(
            (left, right) =>
                byPlace(left.position, right.position) || left.location.name.localeCompare(right.location.name),
        )
        .slice(0, DEFINITION_RESULT_LIMIT)
        .map(({ location, position }, at) => ({
            home: nodeHref(location.file, location.line),
            position,
            section: definitionSection(location, at),
            sectionIndex: at,
            source: DEFINITION_SOURCE,
            sourceIndex: index.sources.length,
        }));
};

const compareHits = function compareHits(left: SearchHit, right: SearchHit): number {
    return (
        byPlace(left.position, right.position) ||
        left.sourceIndex - right.sourceIndex ||
        left.sectionIndex - right.sectionIndex
    );
};

export const spreadsOf = function spreadsOf(hits: readonly SearchHit[]): readonly SearchSpread[] {
    const spreads: OpenSpread[] = [];
    for (const hit of hits) {
        const last = spreads.at(-1);
        if (last?.source === hit.source) {
            last.sections.push(hit.section);
            continue;
        }
        spreads.push({ home: hit.home, sections: [hit.section], source: hit.source });
    }
    return spreads;
};

export const searchSpreads = function searchSpreads(corpus: SearchCorpus, query: string): readonly SearchSpread[] {
    const terms = queryTerms(query);
    if (terms.length === 0) {
        return [];
    }
    const index = indexOf(corpus);
    return spreadsOf([...sectionHits(index, terms), ...definitionHits(index, corpus, terms)].toSorted(compareHits));
};