PageSourceSearch

https://bitontolive.it/wp-content/themes/livenetwork-theme/inc/blocks/live-search/view.js?ver=7.1.2

js bitontolive.it collected 2026-10-02 18:49:43 UTC 7,997 bytes, 259 lines download raw bytes

1(function () {
2
3  // === UTILS ===============================================================
4
5  const debounce = (fn, delay = 250) => {
6    let t;
7    return (...args) => {
8      clearTimeout(t);
9      t = setTimeout(() => fn.apply(null, args), delay);
10    };
11  };
12
13  function stripTags(html = '') {
14    const doc = new DOMParser().parseFromString(String(html), 'text/html');
15    return doc.body.textContent || '';
16  }
17
18  function uniqueTerms(terms) {
19    const out = [];
20    const seen = new Set();
21    for (const t of (terms || [])) {
22      const s = String(t).trim();
23      if (!s) continue;
24      const k = s.toLowerCase();
25      if (seen.has(k)) continue;
26      seen.add(k);
27      out.push(s);
28    }
29    return out;
30  }
31
32  function buildHighlightRegex(terms) {
33    const safe = uniqueTerms(terms)
34      .map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
35      .join('|');
36
37    if (!safe) return null;
38    return new RegExp(`(${safe})`, 'gi');
39  }
40
41  function makeHighlightedFragment(text, terms) {
42    const frag = document.createDocumentFragment();
43    const plain = String(text || '');
44
45    const regex = buildHighlightRegex(terms);
46    if (!regex) {
47      frag.appendChild(document.createTextNode(plain));
48      return frag;
49    }
50
51    let lastIndex = 0;
52    let match;
53
54    while ((match = regex.exec(plain)) !== null) {
55      const start = match.index;
56      const end = start + match[0].length;
57
58      if (start > lastIndex) {
59        frag.appendChild(document.createTextNode(plain.slice(lastIndex, start)));
60      }
61
62      const mark = document.createElement('mark');
63      mark.textContent = plain.slice(start, end);
64      frag.appendChild(mark);
65
66      lastIndex = end;
67
68      // Protezione anti-loop (regex global + match vuoti)
69      if (regex.lastIndex === start) regex.lastIndex++;
70    }
71
72    if (lastIndex < plain.length) {
73      frag.appendChild(document.createTextNode(plain.slice(lastIndex)));
74    }
75
76    return frag;
77  }
78
79  function parseTerms(q) {
80    return q
81      .trim()
82      .split(/\s+/)
83      .filter(t => t.length > 1);
84  }
85
86  // === RENDER ==============================================================
87  function renderResults(container, items, terms) {
88    container.textContent = '';
89
90    if (!items || !items.length) {
91      const empty = document.createElement('div');
92      empty.className = 'livenetwork-live-search__empty';
93      empty.textContent = 'Nessun risultato';
94      container.appendChild(empty);
95      return;
96    }
97
98    const frag = document.createDocumentFragment();
99
100    for (const item of items) {
101      const url = (item && item.url) ? String(item.url) : '#';
102      const thumb = (item && item.thumb) ? String(item.thumb).trim() : '';
103
104      const hasThumb = thumb.length > 0;
105
106      const a = document.createElement('a');
107      a.className = 'livenetwork-live-search__item' + (hasThumb ? '' : ' is-no-thumb');
108      a.setAttribute('role', 'option');
109      a.setAttribute('aria-selected', 'false');
110      a.href = url; // DOM API: non rompe HTML anche se url ha caratteri strani
111
112      const textWrap = document.createElement('div');
113      textWrap.className = 'livenetwork-live-search__text';
114
115      // category
116      if (item && item.category) {
117        const cat = document.createElement('div');
118        cat.className = 'livenetwork-live-search__cat';
119        cat.textContent = stripTags(item.category);
120        textWrap.appendChild(cat);
121      }
122
123      // title (highlight sicuro)
124      const title = document.createElement('div');
125      title.className = 'livenetwork-live-search__title';
126      title.appendChild(makeHighlightedFragment(stripTags(item && item.title ? item.title : ''), terms));
127      textWrap.appendChild(title);
128
129      // meta
130      if (item && item.date_h) {
131        const meta = document.createElement('div');
132        meta.className = 'livenetwork-live-search__meta';
133        meta.textContent = stripTags(item.date_h);
134        textWrap.appendChild(meta);
135      }
136
137      // excerpt (highlight sicuro)
138      const excerpt = document.createElement('div');
139      excerpt.className = 'livenetwork-live-search__excerpt';
140      excerpt.appendChild(makeHighlightedFragment(stripTags(item && item.excerpt ? item.excerpt : ''), terms));
141      textWrap.appendChild(excerpt);
142
143      a.appendChild(textWrap);
144
145      // thumb
146      if (hasThumb) {
147        const img = document.createElement('img');
148        img.className = 'livenetwork-live-search__thumb';
149        img.alt = '';
150        img.src = thumb; // DOM API
151        a.appendChild(img);
152      }
153
154      frag.appendChild(a);
155    }
156
157    container.appendChild(frag);
158  }
159
160  // === INIT ================================================================
161  function initLiveSearch(root) {
162    const input     = root.querySelector('.livenetwork-live-search__input');
163    const resultsEl = root.querySelector('.livenetwork-live-search__results');
164    const spinner   = root.querySelector('.livenetwork-live-search__spinner');
165    const form      = root.querySelector('.live-search-form');
166    const endpoint  = root.dataset.endpoint;
167
168    const perPage  = parseInt(root.dataset.perPage || '10', 10);
169    const minChars = parseInt(root.dataset.minChars || '2', 10);
170
171    if (!input || !resultsEl || !endpoint) return;
172
173    let controller = null;
174    let requestSeq = 0;
175
176    const resetUI = () => {
177      resultsEl.textContent = '';
178      spinner && spinner.classList.remove('is-loading');
179      input.setAttribute('aria-expanded', 'false');
180      resultsEl.removeAttribute('aria-busy');
181    };
182
183    const doSearch = debounce(async (q) => {
184      q = (q || '').trim();
185
186      if (q.length < minChars) {
187        if (controller) controller.abort();
188        resetUI();
189        return;
190      }
191
192      const terms = parseTerms(q);
193
194      // abort precedente + seq
195      if (controller) controller.abort();
196      controller = new AbortController();
197      const mySeq = ++requestSeq;
198
199      spinner && spinner.classList.add('is-loading');
200      input.setAttribute('aria-expanded', 'true');
201      resultsEl.setAttribute('aria-busy', 'true');
202
203      try {
204        const url = `${endpoint}?s=${encodeURIComponent(q)}&per_page=${perPage}`;
205        const res = await fetch(url, {
206          credentials: 'same-origin',
207          signal: controller.signal
208        });
209
210        if (mySeq !== requestSeq) return;
211
212        const data = await res.json();
213        renderResults(resultsEl, data, terms);
214
215      } catch (e) {
216        if (e && e.name === 'AbortError') return;
217
218        resultsEl.textContent = '';
219        const err = document.createElement('div');
220        err.className = 'livenetwork-live-search__empty';
221        err.textContent = 'Errore nella ricerca';
222        resultsEl.appendChild(err);
223
224      } finally {
225        if (mySeq === requestSeq) {
226          spinner && spinner.classList.remove('is-loading');
227          resultsEl.removeAttribute('aria-busy');
228        }
229      }
230    }, 250);
231
232    input.addEventListener('input', (e) => doSearch(e.target.value));
233    input.addEventListener('focus', (e) => doSearch(e.target.value));
234
235    input.addEventListener('keydown', (e) => {
236      if (e.key === 'Enter') {
237        e.preventDefault();
238        const q = input.value.trim();
239        if (q.length >= minChars) {
240          window.location.href = `/search/?q=${encodeURIComponent(q)}&page=1`;
241        }
242      }
243    });
244
245    if (form) {
246      form.addEventListener('submit', (e) => {
247        e.preventDefault();
248        const q = input.value.trim();
249        if (!q.length) return;
250        window.location.href = `/search/?q=${encodeURIComponent(q)}&page=1`;
251      });
252    }
253  }
254
255  document.addEventListener('DOMContentLoaded', () => {
256    document.querySelectorAll('.livenetwork-live-search').forEach(initLiveSearch);
257  });
258
259})();

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.