1/* Site-wide public search. No index writes, third-party browser calls, or private content. */ 2(() => { 3 'use strict'; 4 const copy = { 5 ar: {title:'Ø§ÙØ¨ØØ« Ù٠اÙÙ ÙÙØ¹',placeholder:'Ø§Ø¨ØØ« ÙÙ Ø¬Ù ÙØ¹ Ù ØØªÙÙØ§Øª اÙÙ ÙÙØ¹â¦',hint:'اÙÙØªØ¨Ø اÙÙØ±Ø¢Ù ÙØªÙØ³ÙØ±ÙØ Ø§ÙØØ¯ÙØ«Ø اÙ٠صاØÙ ÙØ§ÙØªÙØ§Ùات',search:'Ø¨ØØ«',close:'Ø¥ØºÙØ§Ù Ø§ÙØ¨ØØ«',intro:'Ø§ÙØªØ¨ ÙÙ٠تÙÙ Ø£Ù ØØ±ÙÙ٠عÙÙ Ø§ÙØ£ÙÙ ÙØ¨Ø¯Ø¡ Ø§ÙØ¨ØØ«.',loading:'Ø¬Ø§Ø±Ù Ø§ÙØ¨ØØ«â¦',empty:'ÙØ§ ØªÙØ¬Ø¯ ÙØªØ§Ø¦Ø¬ ÙÙ ÙØ°Ø§ اÙÙØ³Ù .',error:'ØªØ¹Ø°ÙØ± Ø§ÙØ¨ØØ« Ù Ø¤ÙØªÙا. ØØ§Ù٠٠رة أخرÙ.',more:'عرض اÙÙ Ø²ÙØ¯',retry:'إعادة اÙÙ ØØ§ÙÙØ©',content:'اÙÙØªØ¨ ÙÙ ØØªÙ٠اÙÙ ÙÙØ¹',quran:'اÙÙØ±Ø¢Ù ÙØªÙØ³ÙØ±Ù ÙØªØ±Ø¬Ù اتÙ',hadeeth:'Ù ÙØ³Ùعة Ø§ÙØØ¯ÙØ«',catalog:'Ø§ÙØ³Ùر ÙØ§Ù٠صاØÙ ÙØ§ÙØªÙØ§Ùات',books:'ÙØªØ§Ø¨',surahs:'Ø³ÙØ±Ø©',mushafs:'٠صØÙ',audio:'ØªÙØ§ÙØ©',translation:'ترج٠ة',tafsir:'ØªÙØ³Ùر',glossary:'٠عاÙ٠اÙÙÙ٠ات'}, 6 en: {title:'Search the site',placeholder:'Search all site contentâ¦',hint:'Books, Quran, Hadeeth, Mushafs and recitations',search:'Search',close:'Close search',intro:'Enter at least two characters to search.',loading:'Searchingâ¦',empty:'No results in this section.',error:'Search is temporarily unavailable. Please retry.',more:'Show more',retry:'Retry',content:'Books & site content',quran:'Quran, Tafsir & translations',hadeeth:'Hadeeth Encyclopedia',catalog:'Surahs, Mushafs & recitations',books:'Book',surahs:'Surah',mushafs:'Mushaf',audio:'Recitation',translation:'Translation',tafsir:'Tafsir',glossary:'Word meanings'} 7 }; 8 const icon = (kind) => { 9 const svg=document.createElementNS('http://www.w3.org/2000/svg','svg');svg.setAttribute('viewBox','0 0 24 24');svg.setAttribute('aria-hidden','true'); 10 svg.innerHTML=kind==='close'?'<path d="m6 6 12 12M6 18 18 6"/>':'<circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 5 5"/>'; 11 return svg; 12 }; 13 function ready() { 14 const inner=document.querySelector('.irhp-site-header__inner'); 15 if(!inner||inner.querySelector('.irqc-header-search')||!window.IRQCSiteSearch?.endpoint)return; 16 const endpoint=new URL(window.IRQCSiteSearch.endpoint,location.href);if(endpoint.origin!==location.origin)return; 17 const form=document.createElement('form');form.className='irqc-header-search';form.setAttribute('role','search'); 18 const field=document.createElement('input');field.type='search';field.maxLength=80;field.autocomplete='off'; 19 const submit=document.createElement('button');submit.type='submit';submit.append(icon('search'));form.append(field,submit);inner.append(form); 20 const dialog=document.createElement('dialog');dialog.className='irqc-site-search-dialog';dialog.setAttribute('aria-labelledby','irqc-site-search-title'); 21 const head=document.createElement('div');head.className='irqc-site-search-head'; 22 const title=document.createElement('h2');title.id='irqc-site-search-title';const close=document.createElement('button');close.type='button';close.className='irqc-site-search-close';close.append(icon('close'));head.append(title,close); 23 const search=document.createElement('form');search.className='irqc-site-search-query';search.setAttribute('role','search'); 24 const input=document.createElement('input');input.type='search';input.maxLength=80;input.autocomplete='off';const go=document.createElement('button');go.type='submit';go.append(icon('search'));search.append(input,go); 25 const hint=document.createElement('p');hint.className='irqc-site-search-hint'; 26 const groups=document.createElement('div');groups.className='irqc-site-search-groups'; 27 dialog.append(head,search,hint,groups);document.body.append(dialog); 28 let labels=copy.en,controller=null,revision=0,timer=null,previousFocus=null; 29 const sections=new Map(); 30 for(const scope of ['content','quran','hadeeth','catalog']){ 31 const section=document.createElement('section');section.dataset.scope=scope; 32 const heading=document.createElement('h3'),list=document.createElement('div'),status=document.createElement('p'),more=document.createElement('button'); 33 status.className='irqc-site-search-status';status.setAttribute('role','status');more.type='button';more.hidden=true; 34 section.append(heading,list,status,more);groups.append(section); 35 const state={scope,heading,list,status,more,cursor:null,count:0,ids:new Set(),busy:false,failed:false};sections.set(scope,state); 36 more.addEventListener('click',()=>load(state,revision,input.value.trim())); 37 } 38 function language(){ 39 const lang=document.querySelector('[data-irqc-app][data-language]')?.dataset.language||document.documentElement.lang||'en'; 40 labels=copy[lang.startsWith('ar')?'ar':'en'];const rtl=labels===copy.ar; 41 form.dir=dialog.dir=rtl?'rtl':'ltr';form.lang=dialog.lang=rtl?'ar':'en';title.textContent=labels.title; 42 for(const f of [field,input]){f.placeholder=labels.placeholder;f.setAttribute('aria-label',labels.title);} 43 for(const b of [submit,go])b.setAttribute('aria-label',labels.search); 44 close.setAttribute('aria-label',labels.close);hint.textContent=labels.hint; 45 sections.forEach(s=>{s.heading.textContent=labels[s.scope];s.more.textContent=labels.more;}); 46 } 47 function stop(){clearTimeout(timer);controller?.abort();controller=null;revision++;} 48 function dismiss(){if(dialog.open)dialog.close();} 49 dialog.addEventListener('close',()=>{stop();document.body.classList.remove('irqc-site-search-open');if(previousFocus?.isConnected)previousFocus.focus({preventScroll:true});}); 50 close.addEventListener('click',dismiss); 51 dialog.addEventListener('keydown',event=>{if(event.key==='Escape'){event.preventDefault();dismiss();}}); 52 dialog.addEventListener('click',event=>{if(event.target!==dialog)return;const r=dialog.getBoundingClientRect();if(event.clientX<r.left||event.clientX>
52r.right||event.clientY<r.top||event.clientY>r.bottom)dismiss();}); 53 function open(){if(dialog.open)return;language();previousFocus=document.activeElement;dialog.showModal();document.body.classList.add('irqc-site-search-open');input.value=field.value;input.focus({preventScroll:true});begin();} 54 form.addEventListener('submit',event=>{event.preventDefault();open();}); 55 field.addEventListener('input',()=>{if(field.value.trim().length>=2)open();}); 56 input.addEventListener('input',()=>{field.value=input.value;stop();timer=setTimeout(begin,300);}); 57 search.addEventListener('submit',event=>{event.preventDefault();field.value=input.value;begin();}); 58 function begin(){ 59 stop();const q=input.value.trim(),mine=revision;controller=new AbortController(); 60 sections.forEach(s=>{s.heading.textContent=labels[s.scope];s.list.replaceChildren();s.ids.clear();s.count=0;s.cursor=null;s.busy=false;s.failed=false;s.more.hidden=true;s.status.textContent=q.length<2?labels.intro:labels.loading;}); 61 if(q.length<2)return; 62 sections.forEach(s=>load(s,mine,q)); 63 } 64 async function load(state,mine,q){ 65 if(state.busy||mine!==revision||!controller)return; 66 state.busy=true;state.more.hidden=true;state.status.textContent=labels.loading; 67 const signal=controller.signal; 68 try { 69 // Continue empty batches; never declare no results while sources remain unsearched. 70 do { 71 const url=new URL(endpoint);url.searchParams.set('q',q);url.searchParams.set('scope',state.scope);if(state.cursor)url.searchParams.set('cursor',state.cursor); 72 const response=await fetch(url,{signal,credentials:'same-origin',headers:{Accept:'application/json'}}); 73 if(!response.ok)throw new Error('search'); 74 const data=await response.json();if(mine!==revision||signal.aborted)return; 75 if(!Array.isArray(data.results)||data.results.length>12||!(data.next_cursor===null||typeof data.next_cursor==='string')||data.complete!==(data.next_cursor===null)||data.next_cursor===state.cursor&&data.next_cursor!==null)throw new Error('response'); 76 for(const result of data.results){ 77 if(typeof result.id!=='string'||typeof result.url!=='string'||typeof result.title!=='string'||state.ids.has(result.id))continue; 78 const target=new URL(result.url,location.href);if(target.origin!==location.origin||!['http:','https:'].includes(target.protocol))continue; 79 const link=document.createElement('a');link.href=target.href;link.className='irqc-site-search-result'; 80 const meta=document.createElement('small');meta.textContent=(labels[result.section]||result.section||'')+(result.language?' · '+result.language:''); 81 const name=document.createElement('strong');name.dir='auto';name.textContent=result.title;link.append(meta,name); 82 if(result.text){const text=document.createElement('p');text.dir='auto';text.textContent=result.text;link.append(text);} 83 link.addEventListener('click',dismiss);state.list.append(link);state.ids.add(result.id);state.count++; 84 } 85 state.cursor=data.next_cursor; 86 }while(state.cursor&&state.count<6&&mine===revision&&!signal.aborted); 87 if(mine!==revision||signal.aborted)return; 88 state.failed=false;state.heading.textContent=labels[state.scope]+(state.count?' ('+state.count+')':'');state.more.textContent=labels.more;state.more.hidden=!state.cursor;state.status.textContent=state.count?'':labels.empty; 89 }catch(error){if(mine!==revision||signal.aborted)return;state.failed=true;state.status.textContent=labels.error;state.more.textContent=labels.retry;state.more.hidden=false;} 90 finally{if(mine===revision)state.busy=false;} 91 } 92 language();sections.forEach(s=>s.status.textContent=labels.intro); 93 document.addEventListener('irqc:navigation',()=>{dismiss();language();}); 94 } 95 if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',ready);else ready(); 96})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.