Skip to content

Commit b29743b

Browse files
committed
feat(search): rank surah matches and order results so Al-Baqarah leads
1 parent 0046612 commit b29743b

1 file changed

Lines changed: 53 additions & 18 deletions

File tree

src/components/quran/QuranSearchOverlay.tsx

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ const TOP_ZONE = 130; // a downward drag starting within this top band opens sea
6666
// overshootClamping kills the spring bounce while keeping the velocity hand-off.
6767
const SPRING = { damping: 26, stiffness: 240, overshootClamping: true } as const;
6868

69+
// Definite articles stripped before ranking so a search for "baqarah"/"بقرة" treats
70+
// Al-Baqarah as an exact/prefix hit, not a mid-name substring. Transliterated uses
71+
// al- and its sun-letter assimilations; Arabic always writes the article as ال.
72+
const SURAH_ARTICLES = ["al-", "an-", "ar-", "as-", "ash-", "at-", "ath-", "ad-", "adh-", "az-"];
73+
const stripArticle = (s: string) => {
74+
for (const a of SURAH_ARTICLES) if (s.startsWith(a)) return s.slice(a.length);
75+
return s;
76+
};
77+
const stripArabicArticle = (s: string) => (s.startsWith("ال") ? s.slice(2) : s);
78+
6979
// Pull-down-to-search: a gesture-driven overlay revealed by dragging down from
7080
// the top of the reader (a top-down shade — search bar first), dismissed by
7181
// swiping up (when the results fit), tapping the backdrop, or the close button.
@@ -171,11 +181,36 @@ const QuranSearchOverlay = forwardRef<QuranSearchHandle, { children: ReactNode }
171181
const q = query.trim().toLowerCase();
172182
const surahHits = useMemo(() => {
173183
if (!q) return [];
174-
return surahs.filter(
175-
(s) => s.nameTransliterated.toLowerCase().includes(q) || s.nameArabic.includes(query.trim())
176-
);
184+
const arq = query.trim();
185+
// 0 = exact, 1 = starts-with, 2 = contains — for transliterated or Arabic name,
186+
// each compared with and without its definite article.
187+
const rank = (s: SurahMeta) => {
188+
const name = s.nameTransliterated.toLowerCase();
189+
const bare = stripArticle(name);
190+
const arName = s.nameArabic;
191+
const arBare = stripArabicArticle(arName);
192+
if (name === q || bare === q || arName === arq || arBare === arq) return 0;
193+
if (
194+
name.startsWith(q) ||
195+
bare.startsWith(q) ||
196+
arName.startsWith(arq) ||
197+
arBare.startsWith(arq)
198+
)
199+
return 1;
200+
return 2;
201+
};
202+
return surahs
203+
.filter((s) => s.nameTransliterated.toLowerCase().includes(q) || s.nameArabic.includes(arq))
204+
.sort((a, b) => rank(a) - rank(b) || a.number - b.number);
177205
}, [q, query, surahs]);
178206

207+
// FTS picks the best 50 matches; re-sort those by mushaf order so earlier surahs
208+
// (e.g. Al-Baqarah) lead the verse results.
209+
const sortedHits = useMemo(
210+
() => [...hits].sort((a, b) => a.surahNumber - b.surahNumber || a.ayahNumber - b.ayahNumber),
211+
[hits]
212+
);
213+
179214
const highlights = useHighlightStore((s) => s.highlights);
180215
const labels = useHighlightStore((s) => s.labels);
181216
const [hlHits, setHlHits] = useState<HighlightHit[]>([]);
@@ -428,20 +463,6 @@ const QuranSearchOverlay = forwardRef<QuranSearchHandle, { children: ReactNode }
428463
keyboardDismissMode="on-drag">
429464
{hasQuery && (
430465
<>
431-
{hlHits.length > 0 && (
432-
<>
433-
<SectionLabel chrome={chrome} text={t("quran.library.highlights")} />
434-
{hlHits.map((h) => (
435-
<HighlightRow
436-
key={`h-${h.surah}:${h.ayah}`}
437-
chrome={chrome}
438-
hit={h}
439-
label={labels[h.color] ?? t(`quran.highlight.color.${h.color}`)}
440-
onPress={() => jumpTo(h.page, { surah: h.surah, ayah: h.ayah })}
441-
/>
442-
))}
443-
</>
444-
)}
445466
{bmHits.length > 0 && (
446467
<>
447468
<SectionLabel chrome={chrome} text={t("quran.library.bookmarks")} />
@@ -456,6 +477,20 @@ const QuranSearchOverlay = forwardRef<QuranSearchHandle, { children: ReactNode }
456477
))}
457478
</>
458479
)}
480+
{hlHits.length > 0 && (
481+
<>
482+
<SectionLabel chrome={chrome} text={t("quran.library.highlights")} />
483+
{hlHits.map((h) => (
484+
<HighlightRow
485+
key={`h-${h.surah}:${h.ayah}`}
486+
chrome={chrome}
487+
hit={h}
488+
label={labels[h.color] ?? t(`quran.highlight.color.${h.color}`)}
489+
onPress={() => jumpTo(h.page, { surah: h.surah, ayah: h.ayah })}
490+
/>
491+
))}
492+
</>
493+
)}
459494
{surahHits.length > 0 && (
460495
<>
461496
<SectionLabel chrome={chrome} text={t("quran.browse.surahs")} />
@@ -472,7 +507,7 @@ const QuranSearchOverlay = forwardRef<QuranSearchHandle, { children: ReactNode }
472507
{hits.length > 0 && (
473508
<>
474509
<SectionLabel chrome={chrome} text={t("quran.browse.verses")} />
475-
{hits.map((hit) => (
510+
{sortedHits.map((hit) => (
476511
<VerseRow
477512
key={`v-${hit.surahNumber}:${hit.ayahNumber}`}
478513
chrome={chrome}

0 commit comments

Comments
 (0)