PageSourceSearch

https://www.franzoesischkochen.de/wp-content/mu-plugins/fk-blog-search/fk-blog-search.js?ver=1788081684

js franzoesischkochen.de collected 2026-10-02 11:45:12 UTC 8,982 bytes, 145 lines download raw bytes

1/* FK Blog Search – Live-Dropdown (Rezepte + Kategorien; bei Typ "Produkte" Shop-Suche) */
2(function () {
3  'use strict';
4  if (typeof FK_BSEARCH === 'undefined') return;
5  var CFG = FK_BSEARCH, L = CFG.labels;
6  var inputs = document.querySelectorAll('form.search-form input[name="s"]');
7  if (!inputs.length) return;
8
9  function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
10  function el(html) { var d = document.createElement('div'); d.innerHTML = html; return d.firstElementChild; }
11  function section(label, inner, cls) { return '<div class="fk-s-section ' + (cls || '') + '"><span class="fk-s-label">' + esc(label) + '</span>' + inner + '</div>'; }
12  function chips(items) { return '<div class="fk-chips">' + items.map(function (i) { return '<a href="' + esc(i.url) + '">' + esc(i.label) + '</a>'; }).join('') + '</div>'; }
13  function catList(cats) {
14    return '<ul class="fk-s-cats">' + cats.map(function (c) {
15      return '<li><a href="' + esc(c.url) + '">' + (c.parent ? '<span class="fk-s-parent">' + esc(c.parent) + ' ›</span> ' : '') + esc(c.name) + '</a></li>';
16    }).join('') + '</ul>';
17  }
18  function itemList(items, cls) {
19    return '<ul class="fk-s-products ' + cls + '">' + items.map(function (p) {
20      return '<li><a href="' + esc(p.url) + '"><span class="fk-s-img">' + (p.img ? '<img src="' + esc(p.img) + '" alt="" loading="lazy">' : '') + '</span><span class="fk-s-title">' + p.title + (p.desc ? '<span class="fk-s-desc">' + esc(p.desc) + '</span>' : '') + '</span></a></li>';
21    }).join('') + '</ul>';
22  }
23
24  function initialHtml() {
25    var h = '';
26    if (CFG.pageCats && CFG.pageCats.length) h += section(L.cats, catList(CFG.pageCats));
27    else if (CFG.popular && CFG.popular.length) h += section(L.popular, chips(CFG.popular));
28    if (CFG.help && CFG.help.length) h += section(L.help, chips(CFG.help), 'fk-s-help');
29    return h;
30  }
31
32  function recipesHtml(d) {
33    var h = '';
34    if (d.recipes && d.recipes.length) {
35      h += section(L.recipes, itemList(d.recipes, 'fk-s-recipes'));
36      h += '<a class="fk-s-all" href="' + esc(d.more) + '">' + esc(L.all) + (d.total > d.recipes.length ? ' (' + d.total + ')' : '') + '</a>';
37      if (d.categories && d.categories.length) h += section(L.cats, catList(d.categories));
38    } else {
39      h += '<p class="fk-s-none">' + esc(L.none) + '</p>';
40      if (d.categories && d.categories.length) h += section(L.cats, catList(d.categories));
41      if (CFG.popular && CFG.popular.length) h += section(L.popular, chips(CFG.popular));
42    }
43    return h;
44  }
45
46  function productsHtml(d) {
47    var h = '';
48    if (d.brand) h += section(L.brand, '<a class="fk-s-brand" href="' + esc(d.brand.url) + '">Alle Produkte von <strong>' + esc(d.brand.name) + '</strong> <span class="fk-s-count">' + d.brand.count + '</span></a>');
49    if (d.products && d.products.length) {
50      h += section(L.products, itemList(d.products, ''));
51      h += '<a class="fk-s-all" href="' + esc(d.more) + '">' + esc(L.all) + (d.total > d.products.length ? ' (' + d.total + ')' : '') + '</a>';
52      if (d.categories && d.categories.length) h += section(L.cats, catList(d.categories));
53    } else {
54      h += '<p class="fk-s-none">' + esc(L.noneProducts) + '</p>';
55      if (d.categories && d.categories.length) h += section(L.cats, catList(d.categories));
56    }
57    return h;
58  }
59
60
61  /* ---- Such-Logging: nur "abgeschlossene" Suchen (1,5 s Ruhe, Klick, Verlassen) ---- */
62  function makeLogger(input, src) {
63    var pending = null, timer = null, sent = '';
64    function send(click) {
65      if (!pending || !CFG.logUrl) return;
66      var key = pending.t + '|' + (click || '');
67      if (key === sent) return; sent = key;
68      var body = JSON.stringify({ t: pending.t, src: pending.src || src, ctx: 'dropdown', hits: pending.hits, click: click || '' });
69      try { if (navigator.sendBeacon) navigator.sendBeacon(CFG.logUrl, new Blob([body], { type: 'application/json' })); else fetch(CFG.logUrl, { method: 'POST', body: body, keepalive: true, headers: { 'Content-Type': 'application/json' } }); } catch (e) {}
70      clearTimeout(timer);
71    }
72    return {
73      shown: function (q, hits, s2) { pending = { t: q, hits: hits, src: s2 }; sent = ''; clearTimeout(timer); timer = setTimeout(function () { send(''); }, 1500); },
74      clear: function () { pending = null; clearTimeout(timer); },
75      click: function (type) { send(type); },
76      flush: function () { send(''); }
77    };
78  }
79
80  Array.prototype.forEach.call(inputs, function (input) {
81    var form = input.form; if (!form) return;
82    input.setAttribute('autocomplete', 'off');
83    form.classList.add('fk-s-wrap');
84    // Panel als "Portal" an <body>: unabhängig von overflow:hidden / display-Regeln der Header-Wrapper
85    var panel = el('<div class="fk-s-panel fk-s-portal" role="listbox" hidden></div>');
86    document.body.appendChild(panel);
87    var select = form.querySelector('.selecttype');
88    var logger = makeLogger(input, 'blog');
89    var timer = null, ctrl = null, lastKey = null, active = -1;
90
91    function mode() { return (select && select.value === 'produkt') ? 'produkt' : 'rezept'; }
92    function place() {
93      var r = form.getBoundingClientRect(), ir = input.getBoundingClientRect();
94      var left = Math.max(8, r.left), width = Math.min(window.innerWidth - left - 8, Math.max(r.width, 280));
95      panel.style.left = Math.round(left + window.scrollX) + 'px';
96      panel.style.top = Math.round(ir.bottom + window.scrollY + 4) + 'px';
97      panel.style.width = Math.round(width) + 'px';
98      panel.style.maxHeight = Math.max(220, window.innerHeight - ir.bottom - 16) + 'px';
99    }
100    function show(html) { panel.innerHTML = '<button type="button" class="fk-s-close" aria-label="Schließen">×</button>' + html; panel.hidden = false; active = -1; place(); }
101    window.addEventListener('resize', function () { if (!panel.hidden) place(); });
102    window.addEventListener('scroll', function () { if (!panel.hidden) place(); }, { passive: true });
103    function hide() { panel.hidden = true; }
104    function items() { return panel.querySelectorAll('.fk-s-products a, .fk-s-cats a, .fk-s-brand, .fk-s-all'); }
105
106    function fetchQ(q, m) {
107      if (ctrl) ctrl.abort();
108      ctrl = ('AbortController' in window) ? new AbortController() : null;
109      var url = (m === 'produkt' ? CFG.shopEndpoint : CFG.endpoint) + '?q=' + encodeURIComponent(q);
110      fetch(url, { signal: ctrl ? ctrl.signal : undefined, credentials: 'same-origin' })
111        .then(function (r) { return r.json(); })
112        .then(function (d) { if (input.value.trim() === q && mode() === m) { show(m === 'produkt' ? productsHtml(d) : recipesHtml(d)); logger.shown(q, d.total || 0, m === 'produkt' ? 'shop' : 'blog'); } })
113        .catch(function (e) { if (e.name !== 'AbortError') hide(); });
114    }
115    function update(force) {
116      var q = input.value.trim(), m = mode(), key = m + '|' + q;
117      if (!force && key === lastKey) return; lastKey = key;
118      if (!q) { logger.clear(); show(initialHtml()); return; }
119      clearTimeout(timer);
120      timer = setTimeout(function () { fetchQ(q, m); }, 120);
121    }
122
123    input.addEventListener('focus', function () { update(true); });
124    input.addEventListener('blur', function () { logger.flush(); });
125    input.addEventListener('input', function () { update(false); });
126    if (select) select.addEventListener('change', function () { if (!panel.hidden) update(true); });
127    input.addEventListener('keydown', function (e) {
128      if (panel.hidden) return;
129      var list = items();
130      if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
131        if (!list.length) return;
132        e.preventDefault();
133        active = (active + (e.key === 'ArrowDown' ? 1 : -1) + list.length) % list.length;
134        Array.prototype.forEach.call(list, function (a, i) { a.classList.toggle('fk-s-active', i === active); });
135        list[active].scrollIntoView({ block: 'nearest' });
136      } else if (e.key === 'Enter' && active >= 0) { e.preventDefault(); window.location.href = list[active].href; }
137      else if (e.key === 'Escape') { hide(); input.blur(); }
138    });
139    document.addEventListener('click', function (e) { if (!form.contains(e.target) && !panel.contains(e.target)) hide(); });
140    panel.addEventListener('mousedown', function (e) { if (e.target.tagName !== 'BUTTON') e.preventDefault(); });
141    panel.addEventListener('click', function (e) {
142      var link = e.target.closest && e.target.closest('a');
143      if (link) { var ty = link.classList.contains('fk-s-all') ? 'all' : link.classList.contains('fk-s-brand') ? 'brand' : link.closest('.fk-s-cats') ? 'category' : link.closest('.fk-s-recipes') ? 'recipe' : link.closest('.fk-s-products') ? 'product' : link.closest('.fk-chips') ? 'popular' : 'other'; logger.click(ty); } if (e.target.classList.contains('fk-s-close')) { hide(); input.blur(); } });
144  });
145})();

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.