PageSourceSearch

https://studio.pxl.to/modules/bind.js

js pxl.to collected 2026-09-24 10:22:11 UTC 6,360 bytes, 234 lines download raw bytes

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.