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));
};