Files
familienarchiv/frontend/src/lib/document/search.ts
Marcel e7f8aa5894 refactor: move document domain core to lib/document/
Moves ~25 components, utils (search, filename, groupDocuments,
documentStatusLabel, validateFile), bulkSelection store, and
TranscriptionSection sub-component. Fixes broken relative imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 13:56:36 +02:00

47 lines
1.8 KiB
TypeScript

export type TextSegment = { text: string; highlight: boolean };
export type MatchOffset = { start: number; length: number };
/**
* Converts a flat string and a list of character-level highlight offsets into
* an array of text segments that can be rendered without {@html}.
*
* Offsets are sorted and merged (overlapping spans become the longest enclosing
* span) before processing. Out-of-bounds offsets are clamped or dropped.
*
* @param text The display text (no delimiter characters).
* @param offsets Character offsets produced by the backend (Java char positions,
* compatible with JavaScript String indexing).
*/
export function applyOffsets(text: string, offsets: MatchOffset[]): TextSegment[] {
if (!offsets.length) return [{ text, highlight: false }];
// Sort by start position and merge overlapping / adjacent spans
const sorted = [...offsets].sort((a, b) => a.start - b.start);
const merged: { start: number; end: number }[] = [];
for (const { start, length } of sorted) {
const end = start + length;
if (end <= 0 || start >= text.length) continue; // completely out of bounds
const clampedStart = Math.max(0, start);
const clampedEnd = Math.min(text.length, end);
const last = merged[merged.length - 1];
if (!last || clampedStart > last.end) {
merged.push({ start: clampedStart, end: clampedEnd });
} else {
last.end = Math.max(last.end, clampedEnd);
}
}
if (!merged.length) return [{ text, highlight: false }];
const segments: TextSegment[] = [];
let pos = 0;
for (const { start, end } of merged) {
if (pos < start) segments.push({ text: text.slice(pos, start), highlight: false });
segments.push({ text: text.slice(start, end), highlight: true });
pos = end;
}
if (pos < text.length) segments.push({ text: text.slice(pos), highlight: false });
return segments;
}