1/** 2 * HomeRunner Toolkit â Lowest Rate Availability (frontend) 3 * 4 * Swaps the explorer card's price for a "No dates available" label on 5 * listings the module flagged as having zero bookable nights 6 * (payload key from config.flag, stamped server-side in Core.php). 7 * 8 * The explorer registers every grid instance on 9 * window.hrExplorer.instances and renders each card through the 10 * instance's getPropertyHtml(). Wrapping that method is the only 11 * per-listing hook the plugin exposes â its `no_dates_price` option is 12 * global, so suppressing the price there would blank every card on the 13 * site. The registry is wrapped in a Proxy so instances registered 14 * before OR after this script runs both get patched; card HTML itself 15 * is never produced before the explorer's first async fetch resolves, 16 * which cannot happen while footer scripts are still executing. 17 * 18 * Config (window.hrtkLraNoDates): 19 * - label string Label text (plain text; inserted via textContent). 20 * - flag string Payload key marking a zero-availability listing. 21 */ 22(function () { 23 'use strict'; 24 25 var cfg = window.hrtkLraNoDates || {}; 26 var label = typeof cfg.label === 'string' ? cfg.label : ''; 27 if (!label) return; 28 29 var FLAG = typeof cfg.flag === 'string' && cfg.flag ? cfg.flag : 'hrtk_no_availability'; 30 31 // Parse the card HTML and replace the price container's content with 32 // the label. DOM parsing rather than a regex: the price container can 33 // hold nested markup (tax-note divs), which a lazy regex mis-slices. 34 function relabel(html) { 35 var tpl = document.createElement('template'); 36 tpl.innerHTML = html; 37 38 var container = tpl.content.querySelector('.hfse-property-price-container'); 39 // No container means the site renders cards without prices 40 // (no_dates_price = "none"); nothing to swap. 41 if (!container) return html; 42 43 var span = document.createElement('span'); 44 span.className = 'hfse-property-price hrtk-no-availability'; 45 span.textContent = label; 46 47 container.textContent = ''; 48 container.appendChild(span); 49 50 return tpl.innerHTML; 51 } 52 53 function patchInstance(inst) { 54 if (!inst || typeof inst.getPropertyHtml !== 'function' || inst.__hrtkNoDatesPatched) { 55 return; 56 } 57 58 var orig = inst.getPropertyHtml; 59 inst.getPropertyHtml = function (property) { 60 var html = orig.apply(this, arguments); 61 // The flag lives top-level on full payloads, but the explorer 62 // fetches with a `_fields` list that strips unknown top-level 63 // keys â there it survives only inside `acf`. Check both. 64 var flagged = property && (property[FLAG] || (property.acf && property.acf[FLAG])); 65 if (!flagged || typeof html !== 'string') { 66 return html; 67 } 68 return relabel(html); 69 }; 70 inst.__hrtkNoDatesPatched = true; 71 } 72 73 // Patch instances already registered, and intercept future 74 // registrations (explorer.js assigns window.hrExplorer.instances[id] 75 // fresh on every init, so a set-trap sees each one). 76 function wrapRegistry(registry) { 77 Object.keys(registry).forEach(function (key) { 78 patchInstance(registry[key]); 79 }); 80 81 return new Proxy(registry, { 82 set: function (target, prop, value) { 83 target[prop] = value; 84 patchInstance(value); 85 return true; 86 } 87 }); 88 } 89 90 window.hrExplorer = window.hrExplorer || { instances: {} }; 91 if (!window.hrExplorer.instances || typeof window.hrExplorer.instances !== 'object') { 92 window.hrExplorer.instances = {}; 93 } 94 window.hrExplorer.instances = wrapRegistry(window.hrExplorer.instances); 95 96 // --- Booking widget --------------------------------------------------- 97 // The widget reads its rates inline from the shortcode config, bypassing 98 // every server-side filter (homerunner-plugin#754), so the flag never 99 // reaches it. The only reachable signal is the rendered price itself: a 100 // "Starting at $0.00" is never legitimate (a zero rate means "no bookable 101 // rate" â same invariant the REST nulling relies on). The widget fills 102 // the server-rendered empty .rent-unit divs client-side and re-fills 103 // them when dates change, so each unit gets a MutationObserver. 104 105 // Zero in any locale/currency: digits present, none of them 1-9. 106 function isZeroPrice(text) { 107 return /\d/.test(text) && !/[1-9]/.test(text); 108 } 109 110 function swapUnit(unit) { 111 var span = document.createElement('span'); 112 span.className = 'hrtk-no-availability'; 113 span.textContent = label; 114 unit.textContent = ''; 115 unit.appendChild(span); 116 } 117 118 function checkUnit(unit) { 119 // Only act on a rendered rent amount, never on loaders or the label 120 // itself (the swap removes .rent-amount, so it can't re-trigger). 121 var amount = unit.querySelector('.rent-amount'); 122 if (amount && isZeroPrice(amount.textContent || '')) { 123 swapUnit(unit); 124 } 125 } 126 127 function watchUnit(unit) { 128 if (unit.__hrtkNoDatesWatched) return; 129 unit.__hrtkNoDatesWatched = true; 130 new MutationObserver(function () { 131 checkUnit(unit); 132 }).observe(unit, { childList: true, subtree: true, characterData: true }); 133 checkUnit(unit); 134 } 135 136 function initWidgetUnits() { 137 var units = document.querySelectorAll('.rent-unit'); 138 for (var i = 0; i < units.length; i++) { 139 watchUnit(units[i]); 140 } 141 } 142 143 initWidgetUnits(); 144 if (document.readyState === 'loading') { 145 document.addEventListener('DOMContentLoaded', initWidgetUnits); 146 } 147})();
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.