PageSourceSearch

https://decorfooditaly.it/wp-content/themes/decorfood/assets/js/ai-product-search.js?ver=1.7.6

js decorfooditaly.it collected 2026-10-02 13:45:20 UTC 5,278 bytes, 181 lines download raw bytes

1document.addEventListener('DOMContentLoaded', () => {
2  const config = window.decorfoodAiSearch || {};
3  const endpoint = config.endpoint;
4  const forms = document.querySelectorAll('[data-ai-product-search]');
5
6  if (!endpoint || !forms.length) {
7    return;
8  }
9
10  const debounce = (callback, wait = 260) => {
11    let timer = null;
12
13    return (...args) => {
14      window.clearTimeout(timer);
15      timer = window.setTimeout(() => callback(...args), wait);
16    };
17  };
18
19  const setStatus = (form, message) => {
20    const status = form.querySelector('[data-ai-search-status]');
21
22    if (status) {
23      status.textContent = message || '';
24    }
25  };
26
27  const clearResults = (form) => {
28    const results = form.querySelector('[data-ai-search-results]');
29
30    if (results) {
31      results.replaceChildren();
32    }
33  };
34
35  const createTextNode = (tagName, className, value) => {
36    const element = document.createElement(tagName);
37
38    if (className) {
39      element.className = className;
40    }
41
42    element.textContent = value || '';
43    return element;
44  };
45
46  const createResultCard = (product) => {
47    const link = document.createElement('a');
48    link.className = 'ai-search-result';
49    link.href = product.url;
50
51    const imageWrap = document.createElement('span');
52    imageWrap.className = 'ai-search-result__image';
53
54    const image = document.createElement('img');
55    image.src = product.image;
56    image.alt = product.title || '';
57    image.loading = 'lazy';
58    imageWrap.appendChild(image);
59
60    const content = document.createElement('span');
61    content.className = 'ai-search-result__content';
62    content.appendChild(createTextNode('span', 'ai-search-result__meta', product.category || 'Decorfood Italy'));
63    content.appendChild(createTextNode('strong', 'ai-search-result__title', product.title));
64    content.appendChild(createTextNode('span', 'ai-search-result__excerpt', product.excerpt));
65
66    const price = createTextNode('span', 'ai-search-result__price', product.price);
67
68    link.append(imageWrap, content, price);
69    return link;
70  };
71
72  const renderResults = (form, payload) => {
73    const results = form.querySelector('[data-ai-search-results]');
74
75    if (!results) {
76      return;
77    }
78
79    results.replaceChildren();
80
81    if (!payload.results || !payload.results.length) {
82      const empty = document.createElement('div');
83      empty.className = 'ai-search-empty';
84      empty.textContent = config.i18n?.empty || 'Nessun prodotto trovato.';
85      results.appendChild(empty);
86      return;
87    }
88
89    payload.results.forEach((product) => {
90      results.appendChild(createResultCard(product));
91    });
92
93    if (payload.search_url && payload.total > payload.results.length) {
94      const more = document.createElement('a');
95      more.className = 'ai-search-result ai-search-result--more';
96      more.href = payload.search_url;
97      more.appendChild(createTextNode('span', 'ai-search-result__content', config.i18n?.allResults || 'Vedi tutti i risultati'));
98      more.appendChild(createTextNode('span', 'ai-search-result__price', String(payload.total)));
99      results.appendChild(more);
100    }
101  };
102
103  const runSearch = async (form, query) => {
104    const trimmedQuery = query.trim();
105
106    if (trimmedQuery.length < 2) {
107      setStatus(form, '');
108      clearResults(form);
109      return;
110    }
111
112    if (form.decorfoodSearchAbort) {
113      form.decorfoodSearchAbort.abort();
114    }
115
116    form.decorfoodSearchAbort = new AbortController();
117    setStatus(form, config.i18n?.loading || 'Ricerca in corso...');
118
119    try {
120      const url = new URL(endpoint);
121      url.searchParams.set('q', trimmedQuery);
122      url.searchParams.set('limit', '6');
123
124      const response = await fetch(url.toString(), {
125        method: 'GET',
126        signal: form.decorfoodSearchAbort.signal,
127        headers: {
128          Accept: 'application/json',
129          'X-WP-Nonce': config.nonce || '',
130        },
131      });
132
133      if (!response.ok) {
134        throw new Error('Search failed');
135      }
136
137      const payload = await response.json();
138      renderResults(form, payload);
139
140      const count = payload.total || 0;
141      const label = count === 1 ? (config.i18n?.oneResult || '1 prodotto consigliato') : `${count} ${config.i18n?.results || 'prodotti consigliati'}`;
142      setStatus(form, count ? label : '');
143    } catch (error) {
144      if (error.name === 'AbortError') {
145        return;
146      }
147
148      setStatus(form, config.i18n?.error || 'Ricerca momentaneamente non disponibile.');
149    }
150  };
151
152  forms.forEach((form) => {
153    const input = form.querySelector('[data-ai-search-input]');
154    const suggestions = form.querySelectorAll('[data-ai-search-suggestion]');
155
156    if (!input) {
157      return;
158    }
159
160    const debouncedSearch = debounce(() => runSearch(form, input.value));
161
162    input.addEventListener('input', debouncedSearch);
163
164    form.addEventListener('submit', (event) => {
165      if (input.value.trim().length < 2) {
166        return;
167      }
168
169      event.preventDefault();
170      runSearch(form, input.value);
171    });
172
173    suggestions.forEach((button) => {
174      button.addEventListener('click', () => {
175        input.value = button.dataset.aiSearchSuggestion || '';
176        input.focus();
177        runSearch(form, input.value);
178      });
179    });
180  });
181});

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.