@@ -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.
6767const 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