PageSourceSearch

https://ar.bd/_astro/analytics.LQmI_ASX.js

js ar.bd collected 2026-10-09 17:43:49 UTC 5,069 bytes, 103 lines download raw bytes

1// Public measurement IDs belong in Analytics.astro. Never put API keys here.
2const campaigns = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
3export function cleanUrl(value, keepCampaigns = false) {
4  try {
5    const url = new URL(value, 'https://ar.bd');
6    if (!['http:', 'https:'].includes(url.protocol)) return '';
7    const query = new URLSearchParams();
8    if (keepCampaigns) for (const key of campaigns) {
9      const v = url.searchParams.get(key);
10      if (v && /^[\w .~-]{1,100}$/.test(v)) query.set(key, v);
11    }
12    return url.origin + url.pathname + (query.size ? '?' + query : '');
13  } catch { return ''; }
14}
15
16export function actionFor(el) {
17  if (!el || el.disabled || el.classList.contains('is-filtered')) return null;
18  const d = el.dataset;
19  if ('askStart' in d) return ['ai_start', {}];
20  if ('group' in d) return ['project_filter', {filter_type:'kind', filter_value:d.group}];
21  if ('topic' in d) return [el.closest('#project-map') ? 'project_filter' : 'note_filter', {filter_type:'topic', filter_value:d.topic}];
22  if ('focusSelect' in d || 'focusNode' in d) return ['project_select', {project_id:d.focusSelect || d.focusNode}];
23  if ('toggleGraph' in d) return ['project_map_toggle', {expanded:el.getAttribute('aria-expanded') === 'true'}];
24  if (el.matches('.story-stop,[data-story-prev],[data-story-next]')) return ['timeline_chapter', {chapter:el.closest('[data-story]')?.querySelector('[role="tab"][aria-selected="true"]')?.id || ''}];
25  if (el.matches('a[href]')) {
26    const url = cleanUrl(el.href);
27    if (!url) return null;
28    const host = new URL(url).hostname;
29    if (host !== location.hostname) return ['outbound_click', {link_url:url, link_domain:host}];
30    if (el.closest('[data-writing-preview],.archive-picks,.archive-list')) return ['article_open', {link_url:url}];
31  }
32  return null;
33}
34
35function start() {
36  const config = document.querySelector('script[data-site-analytics]');
37  if (!config || location.hostname !== 'ar.bd' || navigator.doNotTrack === '1') return;
38  const pageUrl = cleanUrl(location.href, true);
39  const referrer = document.referrer ? cleanUrl(document.referrer) : '';
40  const gaId = config.dataset.gaId;
41  window.dataLayer = window.dataLayer || [];
42  window.gtag = function () { window.dataLayer.push(arguments); };
43  window.gtag('js', new Date());
44  window.gtag('config', gaId, {page_location:pageUrl, page_referrer:referrer,
45    allow_google_signals:false, allow_ad_personalization_signals:false});
46  const ga = document.createElement('script');
47  ga.async = true;
48  ga.src = 'https://www.googletagmanager.com/gtag/js?id=' + encodeURIComponent(gaId);
49  document.head.append(ga);
50
51  const queued = [];
52  let ready = false;
53  // Strip search text, arbitrary URL parameters and fragments while retaining
54  // the campaign fields used for Facebook/LinkedIn attribution.
55  window.arAnalyticsBeforeSend = (_type, payload) => ({...payload,
56    url:cleanUrl(payload.url, true).replace(location.origin, ''),
57    referrer:payload.referrer ? cleanUrl(payload.referrer) : ''});
58  const umami = document.createElement('script');
59  umami.src = 'https://agent-analytics.asif.dev/script.js';
60  umami.defer = true;
61  umami.dataset.websiteId = config.dataset.umamiId;
62  umami.dataset.domains = 'ar.bd';
63  umami.dataset.autoTrack = 'false';
64  umami.dataset.beforeSend = 'arAnalyticsBeforeSend';
65  umami.addEventListener('load', () => {
66    if (!window.umami) return;
67    ready = true;
68    Promise.resolve(window.umami.track()).catch(() => {});
69    for (const event of queued.splice(0)) Promise.resolve(window.umami.track(...event)).catch(() => {});
70  });
71  document.head.append(umami);
72
73  function track(name, data) {
74    window.gtag('event', name, data);
75    if (ready) Promise.resolve(window.umami.track(name, data)).catch(() => {});
76    else if (queued.length < 20) queued.push([name, data]);
77  }
78  // Observe existing controls without altering their behavior or historic pages.
79  // Bubbling runs after the control's own handler updates its state. The event
80  // retains its original target even when the AI launcher replaces its button.
81  document.addEventListener('click', event => {
82    const el = event.target.closest?.('a,button,[role="button"]');
83    if (!el) return;
84    const action = actionFor(el); if (action) track(...action);
85  });
86  document.addEventListener('keydown', event => {
87    const el = event.target.closest?.('[data-focus-node]');
88    if (el && ['Enter',' '].includes(event.key)) {
89      const action = actionFor(el); if (action) track(...action);
90    }
91  });
92  document.addEventListener('toggle', event => {
93    const el = event.target;
94    if (el.matches('details') && el.open) track('content_expand', {section:el.closest('section')?.id || 'content'});
95  }, true);
96  document.addEventListener('change', event => {
97    const el = event.target;
98    if (el.matches('input[type="search"]')) track('site_search', {
99      section:el.closest('section')?.id || 'writing', query_length:el.value.trim().length,
100    });
101  });
102}
103if (typeof document !== 'undefined') start();

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.