1import { buscarCoincidenciasDesdeSiteMap } from './main.js'; 2 3export function insertarBuscadorInput() { 4 const contenedor = document.getElementById('contenedor-buscador'); 5 if (contenedor) { 6 contenedor.innerHTML = ` 7 <span class="bg-slate-200 flex lg:w-max h-max rounded-full cursor-pointer" id="buscador"> 8 <input type="text" id="search" value="" class="!bg-transparent focus:outline-none px-[20px] xl:text-[18px] !border-none pointer-events-none" 9 placeholder="Buscar..." readonly> 10 <button type="button" id="guardarBoton" class="bg-mainColor w-[50px] h-[50px] p-[10px] rounded-full pointer-events-none" aria-label="button"> 11 <i class="fa-solid fa-magnifying-glass !text-white !text-[18px]"></i> 12 </button> 13 </span> 14 `; 15 16 contenedor.querySelector('#buscador')?.addEventListener('click', () => { 17 crearBuscadorModal(); 18 }); 19 } 20} 21 22export const crearBuscadorModal = () => { 23 if (document.getElementById('modal-buscador')) return; 24 25 const overlay = document.createElement('div'); 26 overlay.id = 'modal-buscador'; 27 overlay.className = ` 28 fixed inset-0 z-[999999] flex items-center justify-center bg-black/40 backdrop-blur-sm 29 `; 30 31 const modal = document.createElement('div'); 32 modal.className = ` 33 bg-white rounded-2xl shadow-xl w-full max-w-xl px-6 pt-8 pb-6 relative flex flex-col 34 `; 35 36 modal.innerHTML = ` 37 <button id="cerrarModalBtn" class="absolute top-3 right-3 w-8 h-8 flex items-center justify-center 38 bg-red-100 text-red-600 rounded-full hover:bg-red-600 hover:text-white"> 39 <i class="fa-solid fa-xmark text-xl"></i> 40 </button> 41 42 <div class="flex flex-col items-center text-center mb-6"> 43 <img src="/SiteAssets/v1/assets/img/header-footer/escudo-municipio.png" alt="Escudo AlcaldÃa" class="w-[35%] h-full mb-2"> 44 <h2 class="text-lg font-semibold text-gray-700">Buscador de Micrositio</h2> 45 <p class="text-sm text-gray-600 mt-1 max-w-md"> 46 Utiliza nuestro buscador para encontrar información relevante dentro de este micrositio. 47 </p> 48 </div> 49 50 <div class="flex gap-2 items-center mb-4"> 51 <div class="relative flex-1"> 52 <input id="inputBusqueda" type="text" placeholder="Buscar contenido..." 53 class="w-full pr-10 px-4 py-2 text-sm border border-gray-300 rounded-lg 54 focus:outline-none focus:ring-2 focus:ring-blue-500" /> 55 <button id="clearBtn" 56 class="absolute right-3 top-1/2 -translate-y-1/2 w-6 h-6 hidden items-center justify-center 57 bg-gray-100 text-gray-600 rounded-full hover:bg-gray-300"> 58 <i class="fa-solid fa-trash-can"></i> 59 </button> 60 </div> 61 <button id="micBtn" class="w-10 h-10 flex items-center justify-center bg-blue-100 text-blue-600 rounded-full hover:bg-blue-500 hover:text-white" title="Hablar"> 62 <i class="fa-solid fa-microphone"></i> 63 </button> 64 </div> 65 66 <div id="loader" class="loader mx-auto my-6" style="display: none;"></div> 67 68 <div id="resultados" class="mt-4 max-h-80 overflow-y-auto divide-y divide-gray-100 69 text-sm text-gray-800 flex-1"></div> 70 71 <div class="mt-4 text-xs flex justify-between"> 72 <span id="tiempo-busqueda" class="text-blue-600 font-medium">Tiempo: -</span>
73 <span id="cantidad-resultados" class="text-gray-700 font-medium">Resultados: -</span> 74 </div> 75 `; 76 77 overlay.appendChild(modal); 78 document.body.appendChild(overlay); 79 insertarEstilosLoader(); 80 81 const input = modal.querySelector('#inputBusqueda'); 82 const clearBtn = modal.querySelector('#clearBtn'); 83 const micBtn = modal.querySelector('#micBtn'); 84 const loader = modal.querySelector('#loader'); 85 const resultados = modal.querySelector('#resultados'); 86 const tiempoSpan = modal.querySelector('#tiempo-busqueda'); 87 const cantidadSpan = modal.querySelector('#cantidad-resultados'); 88 const cerrarModalBtn = modal.querySelector('#cerrarModalBtn'); 89 90 cerrarModalBtn.onclick = () => overlay.remove(); 91 92 input.addEventListener('input', () => { 93 clearBtn.style.display = input.value.trim().length > 0 ? 'flex' : 'none'; 94 }); 95 96 clearBtn.addEventListener('click', () => { 97 input.value = ''; 98 clearBtn.style.display = 'none'; 99 resultados.innerHTML = ''; 100 tiempoSpan.textContent = 'Tiempo: -'; 101 cantidadSpan.textContent = 'Resultados: -'; 102 }); 103 104 input.addEventListener('keydown', (e) => { 105 if (e.key === 'Enter') { 106 ejecutarBusqueda(input, resultados, loader, tiempoSpan, cantidadSpan); 107 } 108 }); 109 110 if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) { 111 const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; 112 const recognition = new SpeechRecognition(); 113 recognition.lang = 'es-CO'; 114 recognition.interimResults = false; 115 recognition.maxAlternatives = 1; 116 117 micBtn.addEventListener('click', () => { 118 recognition.start(); 119 micBtn.classList.add('animate-pulse'); 120 }); 121 122 recognition.onresult = (event) => { 123 let textoVoz = event.results[0][0].transcript.trim(); 124 if (textoVoz.endsWith('.')) textoVoz = textoVoz.slice(0, -1); 125 input.value = textoVoz; 126 clearBtn.style.display = 'flex'; 127 ejecutarBusqueda(input, resultados, loader, tiempoSpan, cantidadSpan); 128 }; 129 130 recognition.onerror = () => micBtn.classList.remove('animate-pulse'); 131 recognition.onend = () => micBtn.classList.remove('animate-pulse'); 132 } else { 133 micBtn.disabled = true; 134 micBtn.title = 'Tu navegador no soporta dictado por voz'; 135 } 136}; 137 138function ejecutarBusqueda(input, resultados, loader, tiempoSpan, cantidadSpan) { 139 const texto = input.value.trim(); 140 if (texto.length < 2) return; 141 142 resultados.innerHTML = ''; 143 loader.style.display = 'block'; 144 tiempoSpan.textContent = 'Tiempo: ...'; 145 cantidadSpan.textContent = 'Resultados: ...'; 146 147 const t0 = performance.now(); 148 const coincidencias = buscarCoincidenciasDesdeSiteMap(texto); 149 const t1 = performance.now(); 150 151 loader.style.display = 'none'; 152 153 if (coincidencias.length === 0) { 154 resultados.innerHTML = '<div class="py-2 px-4 text-gray-500">Sin resultados.</div>'; 155 } else { 156 resultados.innerHTML = coincidencias.map(item => ` 157 <div class="px-4 py-3 hover:bg-gray-100"> 158 <div class="flex gap-3 items-start"> 159 <i class="fa-solid fa-globe text-blue-600 mt-1"></i> 160 <div> 161 <p class="text-sm font-semibold text-blue-800 hover:underline underline-offset-4 cursor-pointer" 162 onclick="window.open('${location.origin + item.Url}', '_blank')"> 163 ${item.Title} 164 </p> 165 <p class="text-[13px] text-blue-500 break-all">${location.origin + item.Url}</p> 166 <p class="text-xs text-gray-600">${item.Comments || 'Sin comentarios'}</p> 167 <p class="text-xs text-gray-600">Proviene de <b>${item.Location}</b></p> 168 </div> 169 </div> 170 </div> 171 `).join(''); 172 } 173 174 tiempoSpan.textContent = `Tiempo: ${(t1 - t0).toFixed(1)} ms`; 175 cantidadSpan.textContent = `Resultados: ${coincidencias.length}`; 176} 177 178function insertarEstilosLoader() { 179 const style = document.createElement('style'); 180 style.textContent = ` 181 .loader { 182 width: 48px; 183 height: 48px; 184 border-radius: 50%; 185 display: inline-block; 186 position: relative; 187 border: 3px solid; 188 border-color: #FFF #FFF transparent transparent; 189 box-sizing: border-box; 190 animation: rotation 1s linear infinite; 191 } 192 .loader::after, 193 .loader::before { 194 content: ''; 195 box-sizing: border-box; 196 position: absolute; 197 left: 0; 198 right: 0; 199 top: 0; 200 bottom: 0; 201 margin: auto; 202 border: 3px solid; 203 border-color: transparent transparent #FF3D00 #FF3D00; 204 width: 40px; 205 height: 40px; 206 border-radius: 50%; 207 animation: rotationBack 0.5s linear infinite; 208 transform-origin: center center; 209 } 210 .loader::before { 211 width: 32px; 212 height: 32px; 213 border-color: #FFF #FFF transparent transparent; 214 animation: rotation 1.5s linear infinite; 215 } 216 @keyframes rotation { 217 0% { transform: rotate(0deg); } 218 100% { transform: rotate(360deg); } 219 } 220 @keyframes rotationBack { 221 0% { transform: rotate(0deg); } 222 100% { transform: rotate(-360deg); } 223 } 224 `; 225 document.head.appendChild(style); 226}
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.