1const second = 1000, 2 minute = second * 60, 3 hour = minute * 60, 4 day = hour * 24; 5 6function prependHttps(URL) { 7 if (!/^(?:http\:\/\/|https\:\/\/|mailto\:|tel\:|fb\:)/.test(URL)) { 8 URL = 'https://' + URL; 9 } 10 11 return URL; 12} 13 14function isEmpty(...str) { 15 return str.some((s) => !s || s.length === 0); 16} 17 18function wrap(target, wrapper) { 19 wrapper = wrapper || document.createElement('div'); 20 21 if (target.nextSibling) 22 target.parentNode.insertBefore(wrapper, target.nextSibling); 23 else target.parentNode.appendChild(wrapper); 24 25 return wrapper.appendChild(target); 26} 27 28function cloneAttributes(target, source) { 29 [...source.attributes].forEach((attr) => { 30 target.setAttribute( 31 attr.nodeName === 'id' ? 'data-id' : attr.nodeName, 32 attr.nodeValue 33 ); 34 }); 35} 36 37const querySelectors = [ 38 { 39 type: 'contacts', 40 required: false, 41 selector: '[custom-contact-first]', 42 attributes: [ 43 'custom-contact-first', 44 'custom-contact-last', 45 'custom-contact-email', 46 'custom-contact-website', 47 'custom-contact-company', 48 'custom-contact-phone', 49 'custom-contact-address', 50 'custom-contact-city', 51 'custom-contact-state', 52 'custom-contact-postcode', 53 'custom-contact-country' 54 ], 55 task: (target, ...attributes) => { 56 target.addEventListener( 57 'click', 58 (event) => { 59 event.preventDefault(); 60 61 document.location.href = 62 'data:text/vcard;charset=utf-8,' + 63 encodeURIComponent( 64 `BEGIN:VCARD\nVERSION:3.0\nN:${attributes[1]};${attributes[0]};;\nFN:${attributes[0]} ${attributes[1]}\nTEL;TYPE=WORK,VOICE:${attributes[5]}\nORG:${attributes[4]}\nADR;TYPE=WORK,PREF:;;${attributes[6]};${attributes[7]};${attributes[8]};${attributes[9]};${attributes[10]}\nEMAIL;WORK;INTERNET:${attributes[2]}\nURL;WORK:${attributes[3]}\nEND:VCARD` 65 ); 66 }, 67 true 68 ); 69 } 70 }, 71 { 72 type: 'shares', 73 required: true, 74 selector: '[custom-share-data]', 75 attributes: ['custom-share-data'], 76 task: (target, ...attributes) => { 77 target.addEventListener( 78 'click', 79 (event) => { 80 event.preventDefault(); 81 navigator.share({ 82 url: window.location, 83 title: attributes[0] 84 }); 85 }, 86 true 87 ); 88 } 89 }, 90 { 91 type: 'signups', 92 required: true, 93 selector: '[custom-signup-data]', 94 attributes: ['custom-signup-data'], 95 task: (target, ...attributes) => { 96 const button = target.querySelector('.button'); 97 const text = target.querySelector('.text'); 98 const submit = target.querySelector('.submit'); 99 100 target.addEventListener( 101 'submit', 102 (event) => { 103 event.preventDefault(); 104 const values = Object.fromEntries(new FormData(event.target)); 105 window.pixel('signup', { props: values }); 106 text.textContent = attributes[0]; 107 button.setAttribute('disabled', 'disabled'); 108 }, 109 true 110 ); 111 112 button.addEventListener('click', (event) => { 113 event.preventDefault(); 114 submit.click(); 115 }); 116 } 117 }, 118 { 119 type: 'links', 120 required: false, 121 selector: '[custom-link-data]', 122 attributes: ['custom-link-target', 'custom-link-data'], 123 task: (target, ...attributes) => { 124 const https = prependHttps(attributes[1]); 125 const href = new URL(https).href; 126 127 target.style.cursor = 'pointer'; 128 target.addEventListener('click', (event) => 129 window.open(href, attributes[0]) 130 ); 131 } 132 }, 133 { 134 type: 'coupons', 135 required: true, 136 selector: '[custom-coupon-data]', 137 attributes: ['custom-coupon-data'], 138 task: (target, ...attributes) => { 139 const button = target.querySelector('.button'); 140 const label = target.querySelector('.label'); 141 142 const textContent = label.textContent; 143 144 button.addEventListener('click', (event) => { 145 label.textContent = 'Copied to clipboard'; 146 navigator.clipboard.writeText(attributes[0]); 147 setTimeout(() => { 148 label.textContent = textContent; 149 }, 2000); 150 }); 151 } 152 }, 153 { 154 type: 'countdowns', 155 required: true, 156 selector: '[custom-countdown-data]', 157 attributes: ['custom-countdown-data'], 158 task: (target, ...attributes) => { 159 const update = () => { 160 const now = new Date().getTime(); 161 const update = new Date(attributes[0]).getTime(); 162 const distance = Math.max(0, update - now); 163 164 const [days, hours, minutes, seconds] = 165 target.querySelectorAll('[data-value]'); 166 167 if (distance < 1) clearInterval(handle); 168 169 (days.textContent = Math.floor(distance / day).toFixed()), 170 (hours.textContent = Math.floor((distance % day) / hour).toFixed()), 171 (minutes.textContent = Math.floor( 172 (distance % hour) / minute 173 ).toFixed()), 174 (seconds.textContent = Math.floor( 175 (distance % minute) / second 176 ).toFixed()); 177 }; 178 179 const handle = setInterval(update, 500); 180 update(); 181 } 182 }, 183 { 184 type: 'animations', 185 required: true, 186 selector: '[custom-animation-trigger]', 187 attributes: ['custom-animation-trigger'], 188 task: (target, ...attributes) => { 189 const wrapper = document.createElement('div'); 190 191 target.classList.add('animate', attributes[0]); 192 193 [ 194 'transform', 195 'position', 196 'width', 197 'height', 198 'top', 199 'left', 200 'right', 201 'bottom', 202 'mix-blend-mode' 203 ].map((attr) => { 204 wrapper.style[attr] = target.style[attr]; 205 target.style[attr] = ''; 206 }); 207 208 target.style.position = 'relative'; 209 target.style.width = '100%'; 210 target.style.height = '100%'; 211 212 wrap(target, wrapper); 213 } 214 } 215]; 216 217function bind(event) { 218 const apply = (query) => { 219 document.querySelectorAll(query.selector).forEach((target) => { 220 const attributes = query.attributes.map((a) => target.getAttribute(a)); 221 if (query.required && isEmpty(...attributes)) return;
222 try { 223 query.task(target, ...attributes); 224 } catch (error) { 225 console.error(error); 226 } 227 }); 228 }; 229 230 querySelectors.forEach(apply); 231} 232 233document.addEventListener('DOMContentLoaded', bind); 234console.debug('bound');
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.