PageSourceSearch

https://mountaincabinsutah.com/wp-content/plugins/homerunner-toolk…oking-widget-math-reconcile.js?ver=3.309.18

js mountaincabinsutah.com collected 2026-10-10 02:02:00 UTC 5,407 bytes, 179 lines download raw bytes

1/* global hrtkBWMR */
2/**
3 * Booking Widget Math Reconcile — JS patch.
4 *
5 * The HR plugin's booking widget JS double-subtracts the weekly discount
6 * when `price_with_dates = average_price`:
7 *
8 *   actualPrice (strikethrough) = totalPrice / nights         CORRECT post-discount
9 *   price       (active bold)   = (totalPrice - discount) / nights   double-discounted
10 *
11 * `totalPrice` (rateplan.total or total - taxes) already has the discount
12 * applied server-side, so subtracting `discount` again on the JS side is a
13 * bug. Result: customer sees a per-night number lower than total/nights.
14 *
15 * Fix logic:
16 *   Let S = strike value (HR's actualPrice)  = totalPrice / nights
17 *   Let A = active value (HR's price)        = (totalPrice - discount) / nights
18 *   Then discount/nights = S - A, and
19 *        pre-discount per-night = (totalPrice + discount) / nights = 2S - A
20 *
21 * With showStrikethrough = true (default):
22 *   strike = (2S - A)  pre-discount per-night
23 *   active = S          post-discount per-night
24 *   matches the search results card's strike+active pair
25 *
26 * With showStrikethrough = false:
27 *   active = S, strike removed
28 *
29 * The JS is enqueued conditionally by Core::enqueue_frontend_patch (only
30 * when this module is enabled).
31 */
32(function () {
33	"use strict";
34
35	var settings = (typeof hrtkBWMR !== "undefined" && hrtkBWMR) || {};
36	var showStrikethrough = settings.show_strikethrough !== false; // default true
37
38	function parsePrice(text) {
39		if (!text) return null;
40		var match = String(text).match(/[\d.,]+/);
41		if (!match) return null;
42		var raw = match[0];
43
44		var lastDot = raw.lastIndexOf(".");
45		var lastComma = raw.lastIndexOf(",");
46
47		if (lastDot === -1 && lastComma === -1) {
48			var n = parseFloat(raw);
49			return isNaN(n) ? null : n;
50		}
51
52		var lastSepChar = lastDot > lastComma ? "." : ",";
53		var afterLast = raw.slice(raw.lastIndexOf(lastSepChar) + 1);
54
55		var decimalSep, thousandsSep;
56		if (afterLast.length <= 2) {
57			decimalSep = lastSepChar;
58			thousandsSep = lastSepChar === "." ? "," : ".";
59		} else {
60			thousandsSep = lastSepChar;
61			decimalSep = lastSepChar === "." ? "," : ".";
62		}
63
64		var normalized = raw.split(thousandsSep).join("").replace(decimalSep, ".");
65		var value = parseFloat(normalized);
66		return isNaN(value) ? null : value;
67	}
68
69	function formatLikeTemplate(value, templateText) {
70		var template = String(templateText);
71		var match = template.match(/[\d.,]+/);
72		if (!match) {
73			return value.toFixed(2);
74		}
75
76		var raw = match[0];
77		var prefix = template.slice(0, match.index);
78		var suffix = template.slice(match.index + raw.length);
79
80		var lastDot = raw.lastIndexOf(".");
81		var lastComma = raw.lastIndexOf(",");
82
83		var decimalSep, thousandsSep, decimals;
84		if (lastDot === -1 && lastComma === -1) {
85			decimalSep = ".";
86			thousandsSep = ",";
87			decimals = 0;
88		} else {
89			var lastSepChar = lastDot > lastComma ? "." : ",";
90			var afterLast = raw.slice(raw.lastIndexOf(lastSepChar) + 1);
91			if (afterLast.length <= 2) {
92				decimalSep = lastSepChar;
93				thousandsSep = lastSepChar === "." ? "," : ".";
94				decimals = afterLast.length;
95			} else {
96				thousandsSep = lastSepChar;
97				decimalSep = lastSepChar === "." ? "," : ".";
98				decimals = 2;
99			}
100		}
101
102		var fixed = value.toFixed(decimals);
103		var parts = fixed.split(".");
104		parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSep);
105		return prefix + parts.join(decimalSep) + suffix;
106	}
107
108	function reconcileRentUnit(rentUnit) {
109		if (!rentUnit) return;
110
111		var del = rentUnit.querySelector("del.rent-deleted");
112		var active = rentUnit.querySelector(".rent-after-date.rent-amount");
113
114		// No del = no buggy strike+active pair. Either no discount applied or
115		// HR didn't render a strike — nothing to fix.
116		if (!del || !active) return;
117
118		var strikeText = del.textContent;
119		var activeText = active.textContent;
120		var fingerprint = strikeText + "|" + activeText;
121
122		// Idempotency: skip if we've already reconciled this exact pair
123		if (rentUnit.dataset.hrtkBwmrFor === fingerprint) return;
124
125		if (!showStrikethrough) {
126			// Just promote the strike (correct post-discount value) into the
127			// active span and remove the strike.
128			active.textContent = strikeText;
129			if (del.parentNode) del.parentNode.removeChild(del);
130			rentUnit.dataset.hrtkBwmrFor = "no-strike";
131			return;
132		}
133
134		var S = parsePrice(strikeText);
135		var A = parsePrice(activeText);
136
137		if (S == null || A == null || S <= A) {
138			// Defensive fallback: just promote strike to active.
139			active.textContent = strikeText;
140			if (del.parentNode) del.parentNode.removeChild(del);
141			rentUnit.dataset.hrtkBwmrFor = "fallback";
142			return;
143		}
144
145		var preDiscount = 2 * S - A;
146		var newStrikeText = formatLikeTemplate(preDiscount, strikeText);
147
148		del.textContent = newStrikeText;
149		active.textContent = strikeText;
150		rentUnit.dataset.hrtkBwmrFor = newStrikeText + "|" + strikeText;
151	}
152
153	function sweep(root) {
154		var rentUnits = root.querySelectorAll(".rent-unit");
155		for (var i = 0; i < rentUnits.length; i++) {
156			reconcileRentUnit(rentUnits[i]);
157		}
158	}
159
160	function init() {
161		sweep(document);
162
163		var observer = new MutationObserver(function () {
164			sweep(document);
165		});
166
167		observer.observe(document.body, {
168			childList: true,
169			subtree: true,
170			characterData: true,
171		});
172	}
173
174	if (document.readyState === "loading") {
175		document.addEventListener("DOMContentLoaded", init);
176	} else {
177		init();
178	}
179})();

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.