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,'&').replace(/</g,'<') 185 .replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); 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…</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 →</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.