PageSourceSearch

https://h24info.ma/wp-content/themes/h24info/js/h24-smart-search.js?ver=1.0.0

js h24info.ma collected 2026-09-24 09:25:38 UTC 9,638 bytes, 285 lines download raw bytes

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                '&': '&amp;',
29                '<': '&lt;',
30                '>': '&gt;',
31                "'": '&#039;',
32                '"': '&quot;'
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.