PageSourceSearch

https://linc.biz/wp-content/plugins/roughan-search-v2/assets/js/rcs-script.js?ver=4.0.0

js linc.biz collected 2026-10-06 23:56:03 UTC 8,241 bytes, 239 lines download raw bytes

1/**
2 * Roughan Custom Search – rcs-script.js  v4.7
3 * Global rcsToggle() / rcsClose() called via inline onclick on buttons.
4 * This CANNOT be blocked by Elementor or any container.
5 */
6
7/* ── Global open/close functions ── */
8/* ── Prevent ghost click after touch ── */
9var _rcsTouchHandled = false;
10
11window.rcsToggle = function(e) {
12  if (e) { e.preventDefault(); e.stopPropagation(); }
13
14  /* On mobile a touchend fires rcsToggle via onclick, then a synthetic
15     click fires ~300ms later and would immediately re-toggle (close).
16     Guard against that by ignoring the click if a touch just handled it. */
17  if (e && e.type === 'click' && _rcsTouchHandled) {
18    _rcsTouchHandled = false;
19    return;
20  }
21  if (e && e.type === 'touchend') {
22    _rcsTouchHandled = true;
23    setTimeout(function () { _rcsTouchHandled = false; }, 600);
24  }
25
26  var dd = document.getElementById('rcs-dropdown');
27  if (!dd) return;
28  dd.classList.contains('rcs-open') ? window.rcsClose() : window.rcsOpen();
29};
30
31window.rcsOpen = function() {
32  var dd   = document.getElementById('rcs-dropdown');
33  var trig = document.getElementById('rcs-trigger');
34  if (!dd) return;
35
36  /* Position below header */
37  var top  = rcsHeaderBottom();
38
39  /* Safety: on mobile the header calc can return 0 or a tiny value.
40     Use a minimum of 56px so the dropdown is never hidden under the header. */
41  if (top < 56) top = 56;
42
43  var maxH = window.innerHeight - top;
44  dd.style.top       = top + 'px';
45  dd.style.maxHeight = maxH + 'px';
46
47  dd.classList.add('rcs-open');
48  dd.setAttribute('aria-hidden', 'false');
49  if (trig) trig.setAttribute('aria-expanded', 'true');
50};
51
52window.rcsClose = function(e) {
53  if (e) { e.preventDefault(); e.stopPropagation(); }
54  var dd   = document.getElementById('rcs-dropdown');
55  var trig = document.getElementById('rcs-trigger');
56  var inp  = document.getElementById('rcs-search-input');
57  if (!dd) return;
58  dd.classList.remove('rcs-open');
59  dd.setAttribute('aria-hidden', 'true');
60  if (trig) trig.setAttribute('aria-expanded', 'false');
61  if (inp)  inp.value = '';
62  rcsHideSugg();
63  rcsCurrentQuery = '';
64};
65
66function rcsHeaderBottom() {
67  var bottom   = 0;
68  var selectors = [
69    '[data-elementor-type="header"]',
70    '.elementor-sticky--active',
71    '.hfe-header',
72    '#masthead', '#site-header',
73    'header.site-header', '.site-header',
74    'body > header', 'header',
75  ];
76  for (var i = 0; i < selectors.length; i++) {
77    var els = document.querySelectorAll(selectors[i]);
78    for (var j = 0; j < els.length; j++) {
79      var r = els[j].getBoundingClientRect();
80      if (r.height > 10 && r.top < 200 && r.bottom > bottom) bottom = r.bottom;
81    }
82    if (bottom > 0) break;
83  }
84  var bar = document.getElementById('wpadminbar');
85  if (bar) bottom = Math.max(bottom, bar.getBoundingClientRect().bottom);
86  return Math.round(bottom > 0 ? bottom : 80);
87}
88
89/* ── Init on DOM ready ── */
90document.addEventListener('DOMContentLoaded', function () {
91
92  /* Move dropdown to body so it's never clipped */
93  var dd = document.getElementById('rcs-dropdown');
94  if (!dd) return;
95  document.body.appendChild(dd);
96
97  /* Close on ESC */
98  document.addEventListener('keydown', function (e) {
99    if ((e.key === 'Escape' || e.keyCode === 27) && dd.classList.contains('rcs-open')) {
100      window.rcsClose();
101    }
102  });
103
104  /* Click outside → close (also handles touchend for mobile) */
105  var _rcsJustOpened = false;
106  function rcsOutsideHandler(e) {
107    if (!dd.classList.contains('rcs-open')) return;
108    if (_rcsJustOpened) return; /* ignore the event that opened the dropdown */
109    if (e.target.closest && (e.target.closest('#rcs-dropdown') || e.target.closest('#rcs-trigger'))) return;
110    window.rcsClose();
111  }
112  document.addEventListener('click', rcsOutsideHandler);
113  document.addEventListener('touchend', function(e) {
114    if (!dd.classList.contains('rcs-open')) return;
115    if (_rcsJustOpened) { _rcsJustOpened = false; return; }
116    if (e.target.closest && (e.target.closest('#rcs-dropdown') || e.target.closest('#rcs-trigger'))) return;
117    window.rcsClose();
118  });
119
120  /* Set _rcsJustOpened flag whenever dropdown opens */
121  var _origOpen = window.rcsOpen;
122  window.rcsOpen = function() {
123    _rcsJustOpened = true;
124    setTimeout(function () { _rcsJustOpened = false; }, 400);
125    _origOpen();
126  };
127
128  /* Reposition on resize */
129  window.addEventListener('resize', function () {
130    if (!dd.classList.contains('rcs-open')) return;
131    var top  = rcsHeaderBottom();
132    dd.style.top       = top + 'px';
133    dd.style.maxHeight = (window.innerHeight - top) + 'px';
134  });
135
136  /* ── Search form submit ── */
137  var form = document.getElementById('rcs-banner-form');
138  if (form) {
139    form.addEventListener('submit', function (e) {
140      e.preventDefault();
141      var inp = document.getElementById('rcs-search-input');
142      var q   = inp ? inp.value.trim() : '';
143      if (q) window.location.href = rcsResultsUrl(q);
144    });
145  }
146
147  var rform = document.getElementById('rcs-results-form');
148  if (rform) {
149    rform.addEventListener('submit', function (e) {
150      var ri = document.getElementById('rcs-results-input');
151      if (ri && !ri.value.trim()) e.preventDefault();
152    });
153  }
154
155  /* ── Live suggestions ── */
156  var inp = document.getElementById('rcs-search-input');
157  if (inp) {
158    inp.addEventListener('input', function () {
159      var q = this.value.trim();
160      clearTimeout(rcsTimer);
161      if (!q) { rcsHideSugg(); rcsCurrentQuery = ''; return; }
162      rcsTimer = setTimeout(function () { rcsDoSearch(q); }, 280);
163    });
164  }
165
166});
167
168/* ── Suggestions helpers ── */
169var rcsTimer = null, rcsCurrentQuery = '';
170
171function rcsHideSugg() {
172  var s = document.getElementById('rcs-suggestions');
173  if (s) { s.classList.remove('rcs-sugg-open'); s.innerHTML = ''; }
174}
175
176function rcsResultsUrl(q) {
177  var base = (window.rcsData && window.rcsData.results_url)
178             || (window.location.origin + '/search-results/');
179  return base + (base.indexOf('?') > -1 ? '&' : '?') + 'rcs_q=' + encodeURIComponent(q);
180}
181
182function rcsHe(s) {
183  return String(s)
184    .replace(/&/g,'&amp;').replace(/</g,'&lt;')
185    .replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
186}
187
188function rcsDoSearch(q) {
189  if (q.length < 2) { rcsHideSugg(); return; }
190  if (q === rcsCurrentQuery) return;
191  rcsCurrentQuery = q;
192
193  var s = document.getElementById('rcs-suggestions');
194  if (!s) return;
195  s.innerHTML = '<p class="rcs-sugg-loading">Searching&hellip;</p>';
196  s.classList.add('rcs-sugg-open');
197
198  var xhr = new XMLHttpRequest();
199  xhr.open('POST', (window.rcsData && window.rcsData.ajaxurl) || '/wp-admin/admin-ajax.php');
200  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
201  xhr.onload = function () {
202    try {
203      var res = JSON.parse(xhr.responseText);
204      if (res && res.success) rcsRenderSugg(res.data, q);
205    } catch(e) { rcsHideSugg(); }
206  };
207  xhr.onerror = function () { rcsHideSugg(); };
208  xhr.send(
209    'action=rcs_live_search'
210    + '&nonce=' + encodeURIComponent((window.rcsData && window.rcsData.nonce) || '')
211    + '&q=' + encodeURIComponent(q)
212  );
213}
214
215function rcsRenderSugg(items, q) {
216  var s = document.getElementById('rcs-suggestions');
217  if (!s) return;
218  if (!items.length) {
219    s.innerHTML = '<p class="rcs-sugg-empty">No results for <strong>' + rcsHe(q) + '</strong></p>';
220    s.classList.add('rcs-sugg-open');
221    return;
222  }
223  var html = '';
224  items.forEach(function (item) {
225    var thumb = item.thumb
226      ? '<img src="' + rcsHe(item.thumb) + '" class="rcs-sugg-item__thumb" alt="">'
227      : '<span class="rcs-sugg-item__thumb-placeholder"></span>';
228    html += '<a href="' + rcsHe(item.url) + '" class="rcs-sugg-item">'
229      + thumb
230      + '<div class="rcs-sugg-item__body">'
231      +   '<span class="rcs-sugg-item__type">' + rcsHe(item.type) + '</span>'
232      +   '<div class="rcs-sugg-item__title">' + rcsHe(item.title) + '</div>'
233      +   (item.excerpt ? '<div class="rcs-sugg-item__excerpt">' + rcsHe(item.excerpt) + '</div>' : '')
234      + '</div></a>';
235  });
236  html += '<a href="' + rcsHe(rcsResultsUrl(q)) + '" class="rcs-sugg-footer">View all results &rarr;</a>';
237  s.innerHTML = html;
238  s.classList.add('rcs-sugg-open');
239}

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.