double quotes inclusion
This commit is contained in:
+34
-14
@@ -49,6 +49,26 @@ function normalizeQuotes(s: string): string {
|
|||||||
.replace(/[‘’‚‛′]/g, "'");
|
.replace(/[‘’‚‛′]/g, "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strip IAST / Vedic diacritics for matching. Decomposes via NFD then drops
|
||||||
|
// codepoints in the Combining Diacritical Marks block. Necessary because
|
||||||
|
// Typesense returns snippets from the `_ascii` shadow field (e.g. "Krsna")
|
||||||
|
// while the rendered DOM uses the original IAST form ("Kṛṣṇa") — without
|
||||||
|
// this normalization, a search-result link to KB 1.34 with prefix "Krsna is
|
||||||
|
// so beautiful" never matches the "Kṛṣṇa is so beautiful" in the DOM.
|
||||||
|
//
|
||||||
|
// Length-preserving for NFC input (one NFC char → one ASCII char): NFD
|
||||||
|
// expands an accented char into base + combining mark, the regex deletes
|
||||||
|
// only the combining mark, and the base is left. So position maps built
|
||||||
|
// from the original collapsed string remain valid after this transform.
|
||||||
|
function stripDiacritics(s: string): string {
|
||||||
|
return s.normalize("NFD").replace(/[̀-ͯ]/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combined normalization used on both sides of every match.
|
||||||
|
function normalizeForMatch(s: string): string {
|
||||||
|
return stripDiacritics(normalizeQuotes(s.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode a text fragment from URL params.
|
* Decode a text fragment from URL params.
|
||||||
* Supports `§` (current), `|` (v2 legacy), and `,` (v1 legacy) delimiters.
|
* Supports `§` (current), `|` (v2 legacy), and `,` (v1 legacy) delimiters.
|
||||||
@@ -103,10 +123,10 @@ export function extractMatchedText(
|
|||||||
// Build a single string with paragraph breaks
|
// Build a single string with paragraph breaks
|
||||||
const fullText = plainParagraphs.join(" ");
|
const fullText = plainParagraphs.join(" ");
|
||||||
const normalized = fullText.replace(/\s+/g, " ");
|
const normalized = fullText.replace(/\s+/g, " ");
|
||||||
const normalizedLower = normalizeQuotes(normalized.toLowerCase());
|
const normalizedLower = normalizeForMatch(normalized);
|
||||||
|
|
||||||
const normalizedPrefix = normalizeQuotes(
|
const normalizedPrefix = normalizeForMatch(
|
||||||
fragment.prefix.replace(/\s+/g, " ").toLowerCase(),
|
fragment.prefix.replace(/\s+/g, " "),
|
||||||
);
|
);
|
||||||
let searchFrom = 0;
|
let searchFrom = 0;
|
||||||
let matchStart = -1;
|
let matchStart = -1;
|
||||||
@@ -116,8 +136,8 @@ export function extractMatchedText(
|
|||||||
if (idx === -1) break;
|
if (idx === -1) break;
|
||||||
|
|
||||||
if (fragment.suffix) {
|
if (fragment.suffix) {
|
||||||
const normalizedSuffix = normalizeQuotes(
|
const normalizedSuffix = normalizeForMatch(
|
||||||
fragment.suffix.replace(/\s+/g, " ").toLowerCase(),
|
fragment.suffix.replace(/\s+/g, " "),
|
||||||
);
|
);
|
||||||
const expectedEnd = idx + fragment.length;
|
const expectedEnd = idx + fragment.length;
|
||||||
const suffixSearchStart = Math.max(
|
const suffixSearchStart = Math.max(
|
||||||
@@ -397,15 +417,15 @@ export function findAndHighlightText(fragment: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quote-normalize for matching so the prefix/suffix and the DOM text agree
|
// Normalize for matching so prefix/suffix and DOM text agree on quote style
|
||||||
// even when one side uses straight ASCII quotes and the other uses curly
|
// and diacritics (search snippets come from the `_ascii` shadow field, the
|
||||||
// typographical quotes. Length-preserving, so collapsedPosMap indices stay
|
// DOM renders the original IAST form). Length-preserving for NFC input, so
|
||||||
// valid against rawText.
|
// collapsedPosMap indices remain valid against rawText.
|
||||||
const normalizedFullLower = normalizeQuotes(collapsedNorm.toLowerCase());
|
const normalizedFullLower = normalizeForMatch(collapsedNorm);
|
||||||
|
|
||||||
// Search for the prefix in the full text (case-insensitive)
|
// Search for the prefix in the full text (case-insensitive)
|
||||||
const normalizedPrefix = normalizeQuotes(
|
const normalizedPrefix = normalizeForMatch(
|
||||||
fragment.prefix.replace(/\s+/g, " ").toLowerCase(),
|
fragment.prefix.replace(/\s+/g, " "),
|
||||||
);
|
);
|
||||||
let searchFrom = 0;
|
let searchFrom = 0;
|
||||||
let matchStartNorm = -1;
|
let matchStartNorm = -1;
|
||||||
@@ -416,8 +436,8 @@ export function findAndHighlightText(fragment: {
|
|||||||
|
|
||||||
// If we have a suffix, verify it appears near the expected length
|
// If we have a suffix, verify it appears near the expected length
|
||||||
if (fragment.suffix) {
|
if (fragment.suffix) {
|
||||||
const normalizedSuffix = normalizeQuotes(
|
const normalizedSuffix = normalizeForMatch(
|
||||||
fragment.suffix.replace(/\s+/g, " ").toLowerCase(),
|
fragment.suffix.replace(/\s+/g, " "),
|
||||||
);
|
);
|
||||||
// Search for suffix near the expected end position in the normalized string.
|
// Search for suffix near the expected end position in the normalized string.
|
||||||
// Use a wider tolerance window (±50) since normalization can shift positions.
|
// Use a wider tolerance window (±50) since normalization can shift positions.
|
||||||
|
|||||||
Reference in New Issue
Block a user