mirror of
				https://github.com/HackTricks-wiki/hacktricks.git
				synced 2025-10-10 18:36:50 +00:00 
			
		
		
		
	fix searcher
This commit is contained in:
		
							parent
							
								
									9e634b0394
								
							
						
					
					
						commit
						41532943d8
					
				| @ -1,102 +1,96 @@ | ||||
| /* ht_searcher.js ──────────────────────────────────────────────── | ||||
|    Dual-index Web-Worker search (HackTricks + HackTricks-Cloud) | ||||
|    with loading icon swap ⏳ → 🔍 and proper host prefix for | ||||
|    cloud results (https://cloud.hacktricks.wiki).
 | ||||
| 
 | ||||
|    Dependencies already expected by the theme: | ||||
|      • mark.js | ||||
|      • elasticlunr.min.js          (worker fetches CDN or /elasticlunr.min.js) | ||||
|      • searchindex.js              (local fallback copies for both wikis) | ||||
|    Dual‑index Web‑Worker search (HackTricks + HackTricks‑Cloud) | ||||
|    · keeps working even if one index fails | ||||
|    · cloud results rendered **blue** | ||||
|    · ⏳ while loading → 🔍 when ready | ||||
| */ | ||||
| 
 | ||||
| (() => { | ||||
|   "use strict"; | ||||
| 
 | ||||
|   /* ───────────── 0. Utility (main thread) ─────────────────── */ | ||||
|   /* ───────────── 0. helpers (main thread) ───────────── */ | ||||
|   const clear = el => { while (el.firstChild) el.removeChild(el.firstChild); }; | ||||
| 
 | ||||
|   /* ───────────── 1. Web‑Worker code (as string) ───────────── */ | ||||
|   /* ───────────── 1. Web‑Worker code ─────────────────── */ | ||||
|   const workerCode = ` | ||||
|     /* emulate browser globals inside worker */ | ||||
|     self.window = self; | ||||
|     self.search = self.search || {}; | ||||
|     const abs = p => location.origin + p; | ||||
| 
 | ||||
|     const abs = p => location.origin + p;          /* helper */ | ||||
|     /* 1 — elasticlunr */ | ||||
|     try { importScripts('https://cdn.jsdelivr.net/npm/elasticlunr@0.9.5/elasticlunr.min.js'); } | ||||
|     catch { importScripts(abs('/elasticlunr.min.js')); } | ||||
| 
 | ||||
|     /* 1 ─ elasticlunr (CDN → local) */ | ||||
|     try { | ||||
|       importScripts('https://cdn.jsdelivr.net/npm/elasticlunr@0.9.5/elasticlunr.min.js'); | ||||
|     } catch { | ||||
|       importScripts(abs('/elasticlunr.min.js')); | ||||
|     } | ||||
| 
 | ||||
|     /* 2 ─ helper to load one search index */ | ||||
|     async function loadIndex(remoteRaw, localPath){ | ||||
|     /* 2 — load a single index (remote → local) */ | ||||
|     async function loadIndex(remote, local, isCloud=false){ | ||||
|       let rawLoaded = false; | ||||
|       try { | ||||
|         const r = await fetch(remoteRaw, {mode:'cors'}); | ||||
|         const r = await fetch(remote,{mode:'cors'}); | ||||
|         if (!r.ok) throw new Error('HTTP '+r.status); | ||||
|         importScripts(URL.createObjectURL(new Blob([await r.text()],{type:'application/javascript'}))); | ||||
|       } catch (e) { | ||||
|         console.warn(remoteRaw,'→',e,'. Trying local fallback …'); | ||||
|         importScripts(abs(localPath)); | ||||
|         rawLoaded = true; | ||||
|       } catch(e){ console.warn('remote',remote,'failed →',e); } | ||||
|       if(!rawLoaded){ | ||||
|         try { importScripts(abs(local)); rawLoaded = true; } | ||||
|         catch(e){ console.error('local',local,'failed →',e); } | ||||
|       } | ||||
|       const data = { idxJSON: self.search.index, urls: self.search.doc_urls }; | ||||
|       if(!rawLoaded) return null;                 /* give up on this index */ | ||||
|       const data = { json:self.search.index, urls:self.search.doc_urls, cloud:isCloud }; | ||||
|       delete self.search.index; delete self.search.doc_urls; | ||||
|       return data; | ||||
|     } | ||||
| 
 | ||||
|     /* 3 ─ load BOTH indexes */ | ||||
|     (async () => { | ||||
|       const MAIN_RAW  = 'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/master/searchindex.js'; | ||||
|       const CLOUD_RAW = 'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks-cloud/refs/heads/master/searchindex.js'; | ||||
| 
 | ||||
|       const { idxJSON:mainJSON,  urls:mainURLs  } = await loadIndex(MAIN_RAW , '/searchindex.js'); | ||||
|       const { idxJSON:cloudJSON, urls:cloudURLs } = await loadIndex(CLOUD_RAW, '/searchindex-cloud.js'); | ||||
|       const indices = []; | ||||
|       const main = await loadIndex(MAIN_RAW , '/searchindex.js',        false); if(main)  indices.push(main); | ||||
|       const cloud= await loadIndex(CLOUD_RAW, '/searchindex-cloud.js',  true ); if(cloud) indices.push(cloud); | ||||
| 
 | ||||
|       const mainIdx  = elasticlunr.Index.load(mainJSON); | ||||
|       const cloudIdx = elasticlunr.Index.load(cloudJSON); | ||||
|       const MAX_OUT  = 30; | ||||
|       if(!indices.length){ postMessage({ready:false, error:'no-index'}); return; } | ||||
| 
 | ||||
|       /* build index objects */ | ||||
|       const built = indices.map(d => ({ | ||||
|         idx : elasticlunr.Index.load(d.json), | ||||
|         urls: d.urls, | ||||
|         cloud: d.cloud, | ||||
|         base: d.cloud ? 'https://cloud.hacktricks.wiki/' : '' | ||||
|       })); | ||||
| 
 | ||||
|       /* ✔ notify UI */ | ||||
|       postMessage({ready:true}); | ||||
|       const MAX = 30, opts = {bool:'AND', expand:true}; | ||||
| 
 | ||||
|       /* 4 ─ search handler */ | ||||
|       self.onmessage = ({data:q}) => { | ||||
|         if (!q) { postMessage([]); return; } | ||||
|         const opts = { bool:'AND', expand:true }; | ||||
|         if(!q){ postMessage([]); return; } | ||||
| 
 | ||||
|         function searchAndScale(idx, urls, base=''){ | ||||
|           const res = idx.search(q, opts); | ||||
|           if (!res.length) return []; | ||||
|         const all = []; | ||||
|         for(const s of built){ | ||||
|           const res = s.idx.search(q,opts); | ||||
|           if(!res.length) continue; | ||||
|           const max = res[0].score || 1; | ||||
|           return res.map(r => ({ | ||||
|             normScore: r.score / max, | ||||
|             doc      : idx.documentStore.getDoc(r.ref), | ||||
|             url      : base + urls[r.ref] | ||||
|           })); | ||||
|           res.forEach(r => { | ||||
|             const doc = s.idx.documentStore.getDoc(r.ref); | ||||
|             all.push({ | ||||
|               norm : r.score / max, | ||||
|               title: doc.title, | ||||
|               body : doc.body, | ||||
|               breadcrumbs: doc.breadcrumbs, | ||||
|               url  : s.base + s.urls[r.ref], | ||||
|               cloud: s.cloud | ||||
|             }); | ||||
|           }); | ||||
|         } | ||||
| 
 | ||||
|         const combined = [ | ||||
|           ...searchAndScale(mainIdx , mainURLs , ''), | ||||
|           ...searchAndScale(cloudIdx, cloudURLs, 'https://cloud.hacktricks.wiki/') | ||||
|         ]; | ||||
| 
 | ||||
|         combined.sort((a,b) => b.normScore - a.normScore); | ||||
|         const top = combined.slice(0, MAX_OUT).map(o => ({ | ||||
|           title      : o.doc.title, | ||||
|           body       : o.doc.body, | ||||
|           breadcrumbs: o.doc.breadcrumbs, | ||||
|           url        : o.url | ||||
|         })); | ||||
|         postMessage(top); | ||||
|         all.sort((a,b)=>b.norm-a.norm); | ||||
|         postMessage(all.slice(0,MAX)); | ||||
|       }; | ||||
|     })(); | ||||
|   `;
 | ||||
| 
 | ||||
|   /* ───────────── 2. Spawn worker ─────────────────────────── */ | ||||
|   /* ───────────── 2. spawn worker ───────────── */ | ||||
|   const worker = new Worker(URL.createObjectURL(new Blob([workerCode],{type:'application/javascript'}))); | ||||
| 
 | ||||
|   /* ───────────── 3. DOM refs ─────────────────────────────── */ | ||||
|   /* ───────────── 3. DOM refs ─────────────── */ | ||||
|   const wrap    = document.getElementById('search-wrapper'); | ||||
|   const bar     = document.getElementById('searchbar'); | ||||
|   const list    = document.getElementById('searchresults'); | ||||
| @ -104,126 +98,73 @@ | ||||
|   const header  = document.getElementById('searchresults-header'); | ||||
|   const icon    = document.getElementById('search-toggle'); | ||||
| 
 | ||||
|   /* loading icon */ | ||||
|   const READY_ICON = icon.innerHTML;   /* theme SVG/HTML */ | ||||
|   const READY_ICON = icon.innerHTML; | ||||
|   icon.textContent = '⏳'; | ||||
|   icon.setAttribute('aria-label','Loading search …'); | ||||
| 
 | ||||
|   /* key codes */ | ||||
|   const HOTKEY=83, ESC=27, DOWN=40, UP=38, ENTER=13; | ||||
|   const HOT=83, ESC=27, DOWN=40, UP=38, ENTER=13; | ||||
|   let debounce, teaserCount=0; | ||||
| 
 | ||||
|   /* ───────────── 4. helpers (teaser etc.) ─────────────── */ | ||||
|   /* ───────────── helpers (teaser, metric) ───────────── */ | ||||
|   const escapeHTML = (()=>{const M={'&':'&','<':'<','>':'>','"':'"','\'':'''};return s=>s.replace(/[&<>'"]/g,c=>M[c]);})(); | ||||
| 
 | ||||
|   function metric(c,t){ | ||||
|     return c===0 ? `No search results for '${t}'.` | ||||
|          : c===1 ? `1 search result for '${t}':` | ||||
|                   : `${c} search results for '${t}':`; | ||||
|   } | ||||
|   const URL_MARK='highlight'; | ||||
|   function metric(c,t){return c?`${c} search result${c>1?'s':''} for '${t}':`:`No search results for '${t}'.`;} | ||||
| 
 | ||||
|   function makeTeaser(body,terms){ | ||||
|     const stem=w=>elasticlunr.stemmer(w.toLowerCase()); | ||||
|     const T=terms.map(stem), W_SRCH=40,W_1ST=8,W_NRM=2,WIN=30; | ||||
|     const W=[], sents=body.toLowerCase().split('. '); | ||||
|     let idx=0, v=W_1ST, found=false; | ||||
|     sents.forEach(s=>{ | ||||
|       v=W_1ST; | ||||
|       s.split(' ').forEach(w=>{ | ||||
|         if(w){ | ||||
|           if(T.some(t=>stem(w).startsWith(t))){v=W_SRCH;found=true;} | ||||
|           W.push([w,v,idx]); v=W_NRM; | ||||
|         } | ||||
|         idx+=w.length+1; | ||||
|       }); | ||||
|       idx++; | ||||
|     }); | ||||
|     const T=terms.map(stem),W_S=40,W_F=8,W_N=2,WIN=30; | ||||
|     const W=[],sents=body.toLowerCase().split('. '); | ||||
|     let i=0,v=W_F,found=false; | ||||
|     sents.forEach(s=>{v=W_F; s.split(' ').forEach(w=>{ if(w){ if(T.some(t=>stem(w).startsWith(t))){v=W_S;found=true;} W.push([w,v,i]); v=W_N;} i+=w.length+1; }); i++;}); | ||||
|     if(!W.length) return body; | ||||
|     const win=Math.min(W.length,WIN); | ||||
|     const sums=[W.slice(0,win).reduce((a,[,wt])=>a+wt,0)]; | ||||
|     for(let i=1;i<=W.length-win;i++) | ||||
|       sums[i]=sums[i-1]-W[i-1][1]+W[i+win-1][1]; | ||||
|     const best=found ? sums.lastIndexOf(Math.max(...sums)) : 0; | ||||
|     const out=[]; idx=W[best][2]; | ||||
|     for(let i=best;i<best+win;i++){ | ||||
|       const [w,wt,pos]=W[i]; | ||||
|       if(idx<pos){out.push(body.substring(idx,pos)); idx=pos;} | ||||
|       if(wt===W_SRCH) out.push('<em>'); | ||||
|       out.push(body.substr(pos,w.length)); | ||||
|       if(wt===W_SRCH) out.push('</em>'); | ||||
|       idx=pos+w.length; | ||||
|     } | ||||
|     for(let k=1;k<=W.length-win;k++) sums[k]=sums[k-1]-W[k-1][1]+W[k+win-1][1]; | ||||
|     const best=found?sums.lastIndexOf(Math.max(...sums)):0; | ||||
|     const out=[]; i=W[best][2]; | ||||
|     for(let k=best;k<best+win;k++){const [w,wt,pos]=W[k]; if(i<pos){out.push(body.substring(i,pos)); i=pos;} if(wt===W_S) out.push('<em>'); out.push(body.substr(pos,w.length)); if(wt===W_S) out.push('</em>'); i=pos+w.length;} | ||||
|     return out.join(''); | ||||
|   } | ||||
| 
 | ||||
|   const URL_MARK_PARAM='highlight'; | ||||
|   function formatResult(d,terms){ | ||||
|     const teaser = makeTeaser(escapeHTML(d.body),terms); | ||||
|   function format(d,terms){ | ||||
|     const teaser=makeTeaser(escapeHTML(d.body),terms); | ||||
|     teaserCount++; | ||||
|     const enc = encodeURIComponent(terms.join(' ')).replace(/'/g,'%27'); | ||||
| 
 | ||||
|     /* decide if absolute */ | ||||
|     const absolute = d.url.startsWith('http'); | ||||
|     const parts = d.url.split('#'); if(parts.length===1) parts.push(''); | ||||
|     const base = absolute ? '' : path_to_root; | ||||
|     const href = `${base}${parts[0]}?${URL_MARK_PARAM}=${enc}#${parts[1]}`; | ||||
| 
 | ||||
|     return `<a href="${href}" aria-details="teaser_${teaserCount}">`+ | ||||
|            `${d.breadcrumbs}<span class="teaser" id="teaser_${teaserCount}" aria-label="Search Result Teaser">`+ | ||||
|            `${teaser}</span></a>`; | ||||
|     const enc=encodeURIComponent(terms.join(' ')).replace(/'/g,'%27'); | ||||
|     const parts=d.url.split('#'); if(parts.length===1) parts.push(''); | ||||
|     const abs=d.url.startsWith('http'); | ||||
|     const href=`${abs?'':path_to_root}${parts[0]}?${URL_MARK}=${enc}#${parts[1]}`; | ||||
|     const style=d.cloud?" style=\"color:#1e88e5\"":""; | ||||
|     return `<a href="${href}" aria-details="teaser_${teaserCount}"${style}>`+ | ||||
|            `${d.breadcrumbs}<span class="teaser" id="teaser_${teaserCount}" aria-label="Search Result Teaser">${teaser}</span></a>`; | ||||
|   } | ||||
| 
 | ||||
|   function showUI(show){ | ||||
|     wrap.classList.toggle('hidden',!show); | ||||
|     icon.setAttribute('aria-expanded',show); | ||||
|     if(show){ window.scrollTo(0,0); bar.focus(); bar.select(); } | ||||
|     else{ listOut.classList.add('hidden'); [...list.children].forEach(li=>li.classList.remove('focus')); } | ||||
|   } | ||||
|   function blurBar(){ | ||||
|     const tmp=document.createElement('input'); | ||||
|     tmp.style.cssText='position:absolute;opacity:0;'; | ||||
|     icon.appendChild(tmp); tmp.focus(); tmp.remove(); | ||||
|   } | ||||
|   /* ───────────── UI control ───────────── */ | ||||
|   function showUI(s){wrap.classList.toggle('hidden',!s); icon.setAttribute('aria-expanded',s); if(s){window.scrollTo(0,0); bar.focus(); bar.select();} else {listOut.classList.add('hidden'); [...list.children].forEach(li=>li.classList.remove('focus'));}} | ||||
|   function blur(){const t=document.createElement('input'); t.style.cssText='position:absolute;opacity:0;'; icon.appendChild(t); t.focus(); t.remove();} | ||||
| 
 | ||||
|   /* ───────────── 5. UI events ───────────────────────────── */ | ||||
|   icon.addEventListener('click',()=>showUI(wrap.classList.contains('hidden'))); | ||||
| 
 | ||||
|   document.addEventListener('keydown',e=>{ | ||||
|     if(e.altKey||e.ctrlKey||e.metaKey||e.shiftKey) return; | ||||
|     const inForm=/^(?:input|select|textarea)$/i.test(e.target.nodeName); | ||||
|     if(e.keyCode===HOTKEY && !inForm){ e.preventDefault(); showUI(true); } | ||||
|     else if(e.keyCode===ESC){ e.preventDefault(); showUI(false); blurBar(); } | ||||
|     else if(e.keyCode===DOWN && document.activeElement===bar){ | ||||
|       e.preventDefault(); const first=list.firstElementChild; if(first){ blurBar(); first.classList.add('focus'); } | ||||
|     }else if([DOWN,UP,ENTER].includes(e.keyCode) && document.activeElement!==bar){ | ||||
|       const cur=list.querySelector('li.focus'); if(!cur) return; e.preventDefault(); | ||||
|       if(e.keyCode===DOWN){ const nxt=cur.nextElementSibling; if(nxt){ cur.classList.remove('focus'); nxt.classList.add('focus'); }} | ||||
|       else if(e.keyCode===UP){ const prv=cur.previousElementSibling; cur.classList.remove('focus'); if(prv){ prv.classList.add('focus'); } else { bar.focus(); }} | ||||
|       else { const a=cur.querySelector('a'); if(a) window.location.assign(a.href); } | ||||
|     } | ||||
|     const f=/^(?:input|select|textarea)$/i.test(e.target.nodeName); | ||||
|     if(e.keyCode===HOT && !f){e.preventDefault(); showUI(true);} else if(e.keyCode===ESC){e.preventDefault(); showUI(false); blur();} | ||||
|     else if(e.keyCode===DOWN && document.activeElement===bar){e.preventDefault(); const first=list.firstElementChild; if(first){blur(); first.classList.add('focus');}} | ||||
|     else if([DOWN,UP,ENTER].includes(e.keyCode) && document.activeElement!==bar){const cur=list.querySelector('li.focus'); if(!cur) return; e.preventDefault(); if(e.keyCode===DOWN){const nxt=cur.nextElementSibling; if(nxt){cur.classList.remove('focus'); nxt.classList.add('focus');}} else if(e.keyCode===UP){const prv=cur.previousElementSibling; cur.classList.remove('focus'); if(prv){prv.classList.add('focus');} else {bar.focus();}} else {const a=cur.querySelector('a'); if(a) window.location.assign(a.href);}} | ||||
|   }); | ||||
| 
 | ||||
|   bar.addEventListener('input',e=>{ | ||||
|     clearTimeout(debounce); | ||||
|     debounce=setTimeout(()=>worker.postMessage(e.target.value.trim()),120); | ||||
|   }); | ||||
|   bar.addEventListener('input',e=>{ clearTimeout(debounce); debounce=setTimeout(()=>worker.postMessage(e.target.value.trim()),120); }); | ||||
| 
 | ||||
|   /* ───────────── 6. Worker messages ─────────────────────── */ | ||||
|   /* ───────────── worker messages ───────────── */ | ||||
|   worker.onmessage = ({data}) => { | ||||
|     if(data && data.ready){ | ||||
|       icon.innerHTML=READY_ICON; | ||||
|       icon.setAttribute('aria-label','Open search (S)'); | ||||
|     if(data && data.ready!==undefined){ | ||||
|       if(data.ready){ icon.innerHTML=READY_ICON; icon.setAttribute('aria-label','Open search (S)'); } | ||||
|       else { icon.textContent='❌'; icon.setAttribute('aria-label','Search unavailable'); } | ||||
|       return; | ||||
|     } | ||||
|     const docs=data; | ||||
|     const q = bar.value.trim(); const terms=q.split(/\s+/).filter(Boolean); | ||||
|     const docs=data, q=bar.value.trim(), terms=q.split(/\s+/).filter(Boolean); | ||||
|     header.textContent=metric(docs.length,q); | ||||
|     clear(list); | ||||
|     docs.forEach(d=>{ | ||||
|       const li=document.createElement('li'); | ||||
|       li.innerHTML=formatResult(d,terms); | ||||
|       list.appendChild(li); | ||||
|     }); | ||||
|     docs.forEach(d=>{const li=document.createElement('li'); li.innerHTML=format(d,terms); list.appendChild(li);}); | ||||
|     listOut.classList.toggle('hidden',!docs.length); | ||||
|   }; | ||||
| })(); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user