PageSourceSearch

https://campoalegre-huila.gov.co/Style%20Library/BuscadorInstituci…/buscador-subsitios/assets/js/interface.js

js campoalegre-huila.gov.co collected 2026-09-24 10:18:56 UTC 8,591 bytes, 226 lines download raw bytes

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.