1(function () { 2 'use strict'; 3 4 var config = window.h24SmartSearch || {}; 5 var minChars = parseInt(config.minChars || 2, 10); 6 var modal = null; 7 var input = null; 8 var form = null; 9 var results = null; 10 var status = null; 11 var lastFocus = null; 12 var timer = null; 13 var controller = null; 14 var openedAt = 0; 15 var sessionToken = ''; 16 17 function qs(selector, context) { 18 return (context || document).querySelector(selector); 19 } 20 21 function qsa(selector, context) { 22 return Array.prototype.slice.call((context || document).querySelectorAll(selector)); 23 } 24 25 function escapeHtml(value) { 26 return String(value || '').replace(/[&<>'"]/g, function (char) { 27 return { 28 '&': '&', 29 '<': '<', 30 '>': '>', 31 "'": ''', 32 '"': '"' 33 }[char]; 34 }); 35 } 36 37 function getToken() { 38 try { 39 sessionToken = window.sessionStorage.getItem('h24ss_token') || ''; 40 if (!sessionToken) { 41 if (window.crypto && window.crypto.getRandomValues) { 42 var array = new Uint32Array(4); 43 window.crypto.getRandomValues(array); 44 sessionToken = Array.prototype.map.call(array, function (n) { return n.toString(36); }).join('-'); 45 } else { 46 sessionToken = String(Date.now()) + '-' + String(Math.random()).slice(2); 47 } 48 window.sessionStorage.setItem('h24ss_token', sessionToken); 49 } 50 } catch (e) { 51 sessionToken = String(Date.now()) + '-' + String(Math.random()).slice(2); 52 } 53 return sessionToken; 54 } 55 56 function setStatus(message, loading) { 57 if (!status) return; 58 status.textContent = message || ''; 59 status.classList.toggle('is-loading', !!loading); 60 } 61 62 function setResults(html) { 63 if (results) { 64 results.innerHTML = html || ''; 65 } 66 } 67 68 function normalizeQuery(value) { 69 return String(value || '').replace(/\s+/g, ' ').trim().slice(0, 80); 70 } 71 72 function isBadQuery(value) { 73 var q = normalizeQuery(value); 74 if (q.length < minChars || q.length > 80) return true; 75 if (/https?:\/\/|www\.|[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/i.test(q)) return true; 76 if (/[<>{}\[\]\\]/.test(q)) return true; 77 return false; 78 } 79 80 function renderItems(payload) { 81 var items = payload && payload.items ? payload.items : []; 82 var heading = payload && payload.heading ? payload.heading : 'Suggestions'; 83 84 if (!items.length) { 85 setResults('<div class="h24-smart-search__empty">Aucun résultat rapide. Lancez la recherche complète.</div>'); 86 return; 87 } 88 89 var html = '<p class="h24-smart-search__results-title">' + escapeHtml(heading) + '</p>'; 90 html += '<ul class="h24-smart-search__list">'; 91 92 items.forEach(function (item) { 93 var thumb = item.thumb ? '<img src="' + escapeHtml(item.thumb) + '" alt="" loading="lazy">' : '<span class="h24-smart-search__thumb-placeholder"></span>'; 94 html += '<li class="h24-smart-search__item">'; 95 html += '<a href="' + escapeHtml(item.url) + '" class="h24-smart-search__link">'; 96 html += '<span class="h24-smart-search__thumb">' + thumb + '</span>'; 97 html += '<span class="h24-smart-search__meta">'; 98 html += '<strong>' + escapeHtml(item.title) + '</strong>'; 99 if (item.excerpt) html += '<small>' + escapeHtml(item.excerpt) + '</small>'; 100 if (item.date) html += '<em>' + escapeHtml(item.date) + '</em>'; 101 html += '</span></a></li>'; 102 }); 103 104 html += '</ul>'; 105 setResults(html); 106 } 107 108 function requestSuggestions(query, mode) { 109 if (!config.ajaxUrl || !config.nonce) return; 110 111 if (controller && controller.abort) { 112 controller.abort(); 113 } 114 115 controller = window.AbortController ? new AbortController() : null; 116 117 var body = new URLSearchParams(); 118 body.append('action', 'h24_smart_search'); 119 body.append('nonce', config.nonce); 120 body.append('q', query || ''); 121 body.append('mode', mode || 'search'); 122 body.append('company', ''); 123 body.append('started', String(openedAt || Date.now())); 124 body.append('token', getToken()); 125 126 setStatus('Recherche en coursâ¦', true); 127 128 fetch(config.ajaxUrl, { 129 method: 'POST', 130 credentials: 'same-origin', 131 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, 132 body: body.toString(), 133 signal: controller ? controller.signal : undefined 134 }) 135 .then(function (response) { return response.json(); }) 136 .then(function (json) { 137 if (!json || !json.success) { 138 setStatus((json && json.data && json.data.message) || 'Recherche indisponible.', false); 139 return; 140 } 141 setStatus('', false); 142 renderItems(json.data || {}); 143 }) 144 .catch(function (error) {
145 if (error && error.name === 'AbortError') return; 146 setStatus('Recherche indisponible pour le moment.', false); 147 }); 148 } 149 150 function onInput() { 151 var query = normalizeQuery(input.value); 152 clearTimeout(timer); 153 154 if (!query) { 155 setResults(''); 156 setStatus('', false); 157 timer = setTimeout(function () { requestSuggestions('', 'latest'); }, 150); 158 return; 159 } 160 161 if (query.length < minChars) { 162 setResults(''); 163 setStatus('Ajoutez au moins ' + minChars + ' caractères.', false); 164 return; 165 } 166 167 if (isBadQuery(query)) { 168 setResults(''); 169 setStatus('Recherche invalide.', false); 170 return; 171 } 172 173 timer = setTimeout(function () { 174 requestSuggestions(query, 'search'); 175 }, 260); 176 } 177 178 function openModal(event) { 179 if (event) event.preventDefault(); 180 if (!modal) return; 181 182 lastFocus = document.activeElement; 183 openedAt = Date.now(); 184 getToken(); 185 186 modal.classList.add('is-open'); 187 modal.setAttribute('aria-hidden', 'false'); 188 document.documentElement.classList.add('h24-smart-search-lock'); 189 190 window.setTimeout(function () { 191 input.focus(); 192 input.select(); 193 }, 50); 194 195 if (!input.value) { 196 requestSuggestions('', 'latest'); 197 } 198 } 199 200 function closeModal(event) { 201 if (event) event.preventDefault(); 202 if (!modal) return; 203 204 modal.classList.remove('is-open'); 205 modal.setAttribute('aria-hidden', 'true'); 206 document.documentElement.classList.remove('h24-smart-search-lock'); 207 setStatus('', false); 208 209 if (lastFocus && lastFocus.focus) { 210 lastFocus.focus(); 211 } 212 } 213 214 function submitForm(event) { 215 var query = normalizeQuery(input.value); 216 input.value = query; 217 218 if (isBadQuery(query)) { 219 event.preventDefault(); 220 setStatus('Veuillez saisir une recherche valide avec au moins ' + minChars + ' caractères.', false); 221 input.focus(); 222 return false; 223 } 224 225 var stamp = qs('input[name="h24sst"]', form); 226 if (stamp) stamp.value = getToken(); 227 return true; 228 } 229 230 function trapFocus(event) { 231 if (!modal || !modal.classList.contains('is-open') || event.key !== 'Tab') return; 232 233 var focusable = qsa('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', modal) 234 .filter(function (node) { return !node.disabled && node.offsetParent !== null; }); 235 236 if (!focusable.length) return; 237 238 var first = focusable[0]; 239 var last = focusable[focusable.length - 1]; 240 241 if (event.shiftKey && document.activeElement === first) { 242 event.preventDefault(); 243 last.focus(); 244 } else if (!event.shiftKey && document.activeElement === last) { 245 event.preventDefault(); 246 first.focus(); 247 } 248 } 249 250 function init() { 251 modal = qs('#h24-smart-search-modal'); 252 if (!modal) return; 253 254 input = qs('#h24-smart-search-input', modal); 255 form = qs('#h24-smart-search-form', modal); 256 results = qs('#h24-smart-search-results', modal); 257 status = qs('.h24-smart-search__status', modal); 258 259 qsa('.h24-search, .h24-smart-search-open').forEach(function (button) { 260 button.addEventListener('click', openModal); 261 button.setAttribute('aria-haspopup', 'dialog'); 262 button.setAttribute('aria-controls', 'h24-smart-search-modal'); 263 }); 264 265 qsa('[data-h24-search-close]', modal).forEach(function (button) { 266 button.addEventListener('click', closeModal); 267 }); 268 269 input.addEventListener('input', onInput); 270 form.addEventListener('submit', submitForm); 271 272 document.addEventListener('keydown', function (event) { 273 if (event.key === 'Escape' && modal.classList.contains('is-open')) { 274 closeModal(event); 275 } 276 trapFocus(event); 277 }); 278 } 279 280 if (document.readyState === 'loading') { 281 document.addEventListener('DOMContentLoaded', init); 282 } else { 283 init(); 284 } 285})();
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.