core/analyzers/search.analyzer.ts
core/analyzers/search.analyzer.ts is a file in Bane's Lab Site. 113 lines of code and 20 definitions.
import type { Block, GlossaryBlock } from "#types/block.types";
import { pagePath, tabLink } from "#core/assets/link.assets";
import { ANCHOR_PREFIX } from "#configuration/constants/document.constants";
import type { Evidence } from "#types/evidence.types";
import { SEARCH_IGNORED_KEYS } from "#configuration/constants/search.constants";
import type { SearchSource } from "#types/search.types";
import type { Section } from "#types/document.types";
import { convertMarkup } from "#core/converters/markup.converter";
export const UNPLACED = Number.POSITIVE_INFINITY;
export const FOLLOWS = 0.5;
export const textsOf = function textsOf(value: unknown): readonly string[] {
if (typeof value === "string") {
return [value];
}
if (Array.isArray(value)) {
return value.flatMap((item: unknown) => textsOf(item));
}
if (typeof value === "object" && value !== null) {
return Object.entries(value).flatMap(([key, inner]: readonly [string, unknown]) =>
SEARCH_IGNORED_KEYS.has(key) ? [] : textsOf(inner),
);
}
return [];
};
const pageAnchorPath = function pageAnchorPath(page: string, section: string): string {
return pagePath(page) + ANCHOR_PREFIX + section;
};
export const anchorPath = function anchorPath(source: SearchSource, section: string): string {
return source.first || source.layout === "document"
? pageAnchorPath(source.page, section)
: tabLink(source.page, source.tab.id, section);
};
const chapterPath = function chapterPath(
subject: { readonly page: string; readonly section: string; readonly tab: string },
firstTabs: ReadonlyMap<string, string>,
): string {
return firstTabs.get(subject.page) === subject.tab
? pageAnchorPath(subject.page, subject.section)
: tabLink(subject.page, subject.tab, subject.section);
};
const earliest = function earliest(positions: Map<string, number>, key: string, at: number): void {
positions.set(key, Math.min(positions.get(key) ?? UNPLACED, at));
};
export const routeOf = function routeOf(route: readonly string[]): ReadonlyMap<string, number> {
const positions = new Map<string, number>();
for (const [at, path] of route.entries()) {
earliest(positions, path, at);
}
return positions;
};
const anchorOf = function anchorOf(href: string): string | null {
const cut = href.indexOf(ANCHOR_PREFIX);
return cut === -1 ? null : href.slice(cut + ANCHOR_PREFIX.length);
};
export const anchorsLinkedFrom = function anchorsLinkedFrom(section: Section): readonly string[] {
return textsOf(section)
.flatMap((text) => convertMarkup(text))
.flatMap((run) => {
const anchor = run.href === undefined ? null : anchorOf(run.href);
return anchor === null ? [] : [anchor];
});
};
export const linksOf = function linksOf(
sources: readonly SearchSource[],
route: ReadonlyMap<string, number>,
): ReadonlyMap<string, number> {
const positions = new Map<string, number>();
for (const source of sources) {
for (const section of source.tab.sections) {
const at = route.get(anchorPath(source, section.id));
if (at === undefined) {
continue;
}
for (const anchor of anchorsLinkedFrom(section)) {
earliest(positions, anchor, at + FOLLOWS);
}
}
}
return positions;
};
export const evidenceOf = function evidenceOf(
evidence: readonly Evidence[],
route: ReadonlyMap<string, number>,
firstTabs: ReadonlyMap<string, string>,
): ReadonlyMap<string, number> {
const positions = new Map<string, number>();
for (const { nodes, subject } of evidence) {
const at = subject.kind === "chapter" ? route.get(chapterPath(subject, firstTabs)) : undefined;
if (at === undefined) {
continue;
}
for (const node of nodes) {
if (node.kind === "definition") {
earliest(positions, node.name, at + FOLLOWS);
}
}
}
return positions;
};
export const isGlossary = function isGlossary(block: Block): block is GlossaryBlock {
return block.kind === "glossary";
};
export const anchorsOf = function anchorsOf(section: Section): readonly string[] {
const blocks = [...(section.blocks ?? []), ...section.subsections.flatMap((sub) => sub.blocks ?? [])];
return [
section.id,
...section.subsections.flatMap((sub) => (sub.id === undefined ? [] : [sub.id])),
...blocks.filter(isGlossary).flatMap((block) => block.entries.flatMap((entry) => entry.id ?? [])),
];
};
export const placeOf = function placeOf(anchors: readonly string[], links: ReadonlyMap<string, number>): number {
return Math.min(UNPLACED, ...anchors.map((anchor) => links.get(anchor) ?? UNPLACED));
};