PageSourceSearch

https://www.adventure-life.com/www/assets/js/PhoneNumbers.js?1790807436

js adventure-life.com collected 2026-10-01 10:41:01 UTC 7,360 bytes, 222 lines download raw bytes

1const PhoneNumbers = new (function(){
2
3    // How many levels up the DOM two phone numbers may share an ancestor and
4    // still count as adjacent.
5    const PROXIMITY_LEVELS = 3;
6
7    // Shortest number we treat as a phone number on display. Anything shorter is
8    // an icon only call link or stray digits in the surrounding copy.
9    const MIN_DIGITS = 7;
10
11    function removeElement(el) {
12        let parent = el.parentNode;
13
14        if (parent === null) {
15            return;
16        }
17
18        parent.removeChild(el);
19
20        // Climb while the removal left an empty container behind, stopping at body
21        // and at any node already detached from the document. Removing a container
22        // can strand numbers still queued for removal, and those have a parent that
23        // no longer has one of its own.
24        while (parent !== document.body && parent.parentNode !== null && parent.innerHTML.trim() === '') {
25            const child = parent;
26
27            parent = child.parentNode;
28            parent.removeChild(child);
29        }
30    }
31
32    function removeEmptyElement(el) {
33        if (el.innerHTML === '' && telDigits(el) === '') {
34            removeElement(el);
35        }
36    }
37
38    function normalizeDigits(value) {
39        const digits = (value || '').replace(/\D/g, '');
40
41        // 1.800.344.6118, +18003446118 and 800.344.6118 are the same number.
42        return digits.length === 11 && digits.startsWith('1') ? digits.slice(1) : digits;
43    }
44
45    function phoneDigits(el) {
46        return normalizeDigits(el.textContent);
47    }
48
49    function telDigits(el) {
50        const href = el.getAttribute('href') || '';
51
52        return href.startsWith('tel:') ? normalizeDigits(href) : '';
53    }
54
55    // Two numbers that read alike but dial differently are not duplicates.
56    function telConflicts(a, b) {
57        const aTel = telDigits(a), bTel = telDigits(b);
58
59        return aTel !== '' && bTel !== '' && aTel !== bTel;
60    }
61
62    function ancestorChain(el, levels) {
63        const chain = [];
64
65        for (let node = el; node && chain.length <= levels; node = node.parentElement) {
66            chain.push(node);
67        }
68
69        return chain;
70    }
71
72    // The closest node both elements sit under, or null when they are too far apart.
73    function sharedAncestor(a, b, levels) {
74        const chain = ancestorChain(a, levels);
75
76        return ancestorChain(b, levels).find(node => chain.includes(node)) || null;
77    }
78
79    function isHiddenNode(el, cache) {
80        if (!cache.has(el)) {
81            const style = window.getComputedStyle(el);
82
83            cache.set(el, style.display === 'none' || style.visibility === 'hidden');
84        }
85
86        return cache.get(el);
87    }
88
89    // Is the number hidden by something below the ancestor it shares with its
90    // match? Hiding is only measured up to the shared ancestor so numbers hidden
91    // together, such as a pair inside a closed dropdown, still count as on
92    // display. One hidden on its own is not, so a breakpoint showing only one of
93    // the two is left intact.
94    function isHiddenWithin(el, root, cache) {
95        for (let node = el; node && node !== root; node = node.parentElement) {
96            if (isHiddenNode(node, cache)) {
97                return true;
98            }
99        }
100
101        return false;
102    }
103
104    function removeDuplicates(selector, levels) {
105        const kept = [], hidden = new Map();
106
107        document.querySelectorAll(selector).forEach(el => {
108            // This list was taken before the first removal, and removing a container
109            // takes everything inside it, so entries still to come may already be off
110            // the page. A detached node has no computed style, which reads as on
111            // display, and it would then stand in for a number that is not there.
112            if (!document.contains(el)) {
113                return;
114            }
115
116            const digits = phoneDigits(el);
117
118            // No number on display to repeat, e.g. an icon only call link.
119            if (digits.length < MIN_DIGITS) {
120                return;
121            }
122
123            const duplicate = other => {
124                if (other.digits !== digits || telConflicts(other.el, el)) {
125                    return false;
126                }
127
128                const root = sharedAncestor(other.el, el, levels);
129
130                return root !== null
131                    && !isHiddenWithin(el, root, hidden)
132                    && !isHiddenWithin(other.el, root, hidden);
133            };
134
135            if (kept.some(duplicate)) {
136                removeElement(el);
137                return;
138            }
139
140            kept.push({el, digits});
141        });
142    }
143
144    // The node containing the number on display. A call link wraps anything from a bare
145    // number to a staff photo and a paragraph of copy, so the number is only written
146    // into the element marked for replacement, or into the link itself when the link holds
147    // nothing else. A link with other markup and no marked element is left alone
148    // rather than flattened down to the number.
149    function displaySlot(el) {
150        const marked = el.querySelector('.al-call-main, .al-call-local');
151
152        if (marked !== null) {
153            return marked;
154        }
155
156        return el.children.length === 0 ? el : null;
157    }
158
159    function updateElement(el, phone) {
160        if (el.hasAttribute('href')) {
161            el.href = phone.href;
162        }
163
164        const slot = displaySlot(el);
165
166        // No number on display to replace, e.g. an icon only call link.
167        if (slot === null || phoneDigits(slot).length < MIN_DIGITS) {
168            return;
169        }
170
171        slot.innerHTML = phone.html;
172    }
173
174    function update(phones) {
175        document.querySelectorAll('.al-call')
176            .forEach(el => {
177                const phone = el.classList.contains('al-call-local') ? phones.local : phones.main;
178
179                // A number the site has no value for is left out of the payload rather
180                // than sent empty, so there is nothing to write and whatever the
181                // server rendered stands.
182                if (!phone) {
183                    return;
184                }
185
186                updateElement(el, phone);
187            });
188
189        document.querySelectorAll('.al-call').forEach(removeEmptyElement);
190
191        removeDuplicates('.al-call', PROXIMITY_LEVELS);
192    }
193
194    /**
195    *	write the numbers returned by dynamic content into the page
196    *
197    *	elements left with nothing to display are dropped, as are numbers the update
198    *	leaves repeated close to one another.
199    */
200    this.update = function(phones) {
201        if (!phones) {
202            return;
203        }
204
205        update(phones);
206    };
207
208    /**
209    *	remove phone numbers repeated close to one another on the page
210    *
211    *	the main and local numbers can resolve to the same value, leaving that
212    *	number rendered twice within a single block. the first of each repeated
213    *	number is kept and the rest are removed, along with any container they empty.
214    *
215    *	a repeat must match on both the number on display and the tel: href, so a
216    *	link that reads the same but dials a different number is left alone.
217    */
218    this.removeDuplicates = function(selector, levels) {
219        removeDuplicates(selector || '.al-call', levels || PROXIMITY_LEVELS);
220    };
221
222})();

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.