mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-11 17:52:31 +03:00
Consolidates PR #8660 (matchesAnyToken with full-match priority over token-level OR fallback) with the existing Turkish search normalization shipped on release/v3.8.49. The PR adds a new matchesAnyToken helper used by the providers search to allow space-separated queries (e.g. 'pollinations sambanova') to match when any of the tokens is present. Full query match takes priority so that an exact 'pollinations sambanova' query still hits even if a partial token would have been ambiguous. The unit test file gains 12 PR-side matchesAnyToken tests on top of the 6 release-side Turkish-normalization tests covering the same function, totaling 27 tests. Co-authored-by: maxmad64bis <maxmad64bis@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
/**
|
||
* Türkçe-güvenli metin normalizasyonu, arama ve sıralama yardımcıları.
|
||
*
|
||
* JavaScript'in locale-bağımsız `toLowerCase()`'i Türkçe `İ/I/ı/i`
|
||
* karakterlerini bozar: `"İ".toLowerCase()` → `"i̇"` (U+0069 U+0307
|
||
* combining dot above). Bu, `toLowerCase().includes()` tabanlı aramada
|
||
* Türkçe girdileri eşleşmez kılar.
|
||
*
|
||
* Saf fonksiyonlar — yan etki ve bağımlılık yok.
|
||
*
|
||
* @module shared/utils/turkishText
|
||
*/
|
||
|
||
/** NFD ile katlanmayan Türkçe harfler için açık eşleme. */
|
||
const TR_FOLD: Readonly<Record<string, string>> = {
|
||
ı: "i",
|
||
ş: "s",
|
||
ğ: "g",
|
||
ü: "u",
|
||
ö: "o",
|
||
ç: "c",
|
||
};
|
||
|
||
/**
|
||
* Aksan-duyarsız, Türkçe-güvenli arama anahtarı üretir.
|
||
* `"İstanbul"` ve `"istanbul"` → `"istanbul"`; `"Şarj"` → `"sarj"`.
|
||
* Baştaki ve sondaki boşluklar kırpılır.
|
||
*/
|
||
export function normalizeForSearch(input: string | null | undefined): string {
|
||
if (!input) return "";
|
||
let s = input.toLocaleLowerCase("tr"); // İ→i, I→ı (Türkçe-güvenli)
|
||
s = s.replace(/[ışğüöç]/g, (ch) => TR_FOLD[ch] ?? ch); // açık Türkçe fold
|
||
s = s.normalize("NFD").replace(/[̀-ͯ]/g, ""); // kalan aksanları sil
|
||
return s.trim();
|
||
}
|
||
|
||
/**
|
||
* `text`, `query`'yi (aksan-duyarsız, Türkçe-güvenli) içeriyor mu?
|
||
* Boş sorgu her zaman `true` döner.
|
||
*/
|
||
export function matchesSearch(
|
||
text: string | null | undefined,
|
||
query: string | null | undefined
|
||
): boolean {
|
||
const q = normalizeForSearch(query);
|
||
if (!q) return true;
|
||
return normalizeForSearch(text).includes(q);
|
||
}
|
||
|
||
/**
|
||
* Checks if `text` matches any whitespace-separated token in `query`.
|
||
* Returns `true` if `query` is empty or if any token is found in `text`.
|
||
* Full query match takes priority over token-level OR fallback.
|
||
*/
|
||
export function matchesAnyToken(
|
||
text: string | null | undefined,
|
||
query: string | null | undefined
|
||
): boolean {
|
||
const q = normalizeForSearch(query);
|
||
if (!q) return true;
|
||
const normalizedText = normalizeForSearch(text);
|
||
if (normalizedText.includes(q)) return true;
|
||
return q.split(/\s+/).filter(Boolean).some((token) => normalizedText.includes(token));
|
||
}
|
||
|
||
const trCollator = new Intl.Collator("tr", {
|
||
sensitivity: "base",
|
||
numeric: true,
|
||
});
|
||
|
||
/**
|
||
* Kullanıcı-görünür listeler için Türkçe alfabe + sayısal-duyarlı
|
||
* karşılaştırma. `Array.prototype.sort` ile doğrudan kullanılabilir.
|
||
*
|
||
* `sensitivity: "base"` kullanıldığından aksan/büyük-küçük harf varyantları
|
||
* sıralama açısından eşit sayılır — örneğin `"Şehir"` ile `"şehir"` berabere
|
||
* gelir. Bu karşılaştırıcı görsel liste sıralaması içindir, kesin
|
||
* aksan-farklılaştırmalı sıralama için değil.
|
||
*/
|
||
export function compareTr(a: string | null | undefined, b: string | null | undefined): number {
|
||
return trCollator.compare(a ?? "", b ?? "");
|
||
}
|