PageSourceSearch

https://ddmu.org.ua/modules/ukraine/ai_bot_assistant_d9/js/widget-loader.js?v=tb9zcj

js ddmu.org.ua collected 2026-10-02 21:57:56 UTC 3,924 bytes, 115 lines download raw bytes

1(function (Drupal, drupalSettings) {
2  Drupal.behaviors.aiBotAssistantD9Widget = {
3    attach(context) {
4      if (context !== document || document.querySelector('script[data-ai-bot-assistant-d9="widget-loader"]')) {
5        return;
6      }
7
8      const settings = drupalSettings.aiBotAssistantD9 || {};
9      if (!settings.publicKey) {
10        return;
11      }
12
13      const widgetUrl = versionedWidgetUrl(settings.widgetUrl || 'https://px.ua/widget.js', settings.widgetVersion);
14      if (!widgetUrl) {
15        return;
16      }
17
18      watchAssistantElement();
19
20      const script = document.createElement('script');
21      script.src = widgetUrl;
22      script.dataset.aiBotAssistantD9 = 'widget-loader';
23      script.dataset.aiAssistantKey = settings.publicKey;
24      script.dataset.aiAssistantHighlightColor = settings.highlightColor || '#0f766e';
25      script.dataset.aiAssistantAgentUrl = settings.agentUrl || 'https://px.ua/agent';
26      if (settings.widgetVersion) {
27        script.dataset.aiAssistantAssetsVersion = settings.widgetVersion;
28      }
29      script.async = true;
30      document.head.appendChild(script);
31    },
32  };
33
34  /**
35   * Adds the site-specific exclusion class when the remote widget creates its
36   * custom element.
37   */
38  function watchAssistantElement() {
39    if (addAssistantSkipClass(document)) {
40      return;
41    }
42
43    const observer = new MutationObserver((mutations) => {
44      for (const mutation of mutations) {
45        for (const node of mutation.addedNodes) {
46          if (node.nodeType === Node.ELEMENT_NODE && addAssistantSkipClass(node)) {
47            observer.disconnect();
48            return;
49          }
50        }
51      }
52    });
53
54    observer.observe(document.documentElement, { childList: true, subtree: true });
55  }
56
57  /**
58   * Adds the exclusion class to an assistant element in a DOM subtree.
59   */
60  function addAssistantSkipClass(root) {
61    const assistant = root.matches?.('ai-site-assistant')
62      ? root
63      : root.querySelector?.('ai-site-assistant');
64    if (!assistant) {
65      return false;
66    }
67
68    assistant.classList.add('am-skip');
69    return true;
70  }
71
72  function versionedWidgetUrl(widgetUrl, version) {
73    widgetUrl = widgetUrl.trim();
74    const normalizedVersion = typeof version === 'string' ? version.trim() : '';
75    if (!normalizedVersion || ['undefined', 'null'].includes(normalizedVersion.toLowerCase())) {
76      return safeWidgetUrl(widgetUrl);
77    }
78    try {
79      const url = new URL(widgetUrl, window.location.href);
80      if (!['http:', 'https:'].includes(url.protocol)) {
81        return '';
82      }
83      url.searchParams.set('v', normalizedVersion);
84      return url.href;
85    } catch (error) {
86      if (/^\s*#/.test(widgetUrl) || (/^[a-z][a-z0-9+.-]*:/i.test(widgetUrl) && !/^https?:/i.test(widgetUrl))) {
87        return '';
88      }
89      const hashIndex = widgetUrl.indexOf('#');
90      const baseUrl = hashIndex === -1 ? widgetUrl : widgetUrl.slice(0, hashIndex);
91      const hash = hashIndex === -1 ? '' : widgetUrl.slice(hashIndex);
92      const queryIndex = baseUrl.indexOf('?');
93      const path = queryIndex === -1 ? baseUrl : baseUrl.slice(0, queryIndex);
94      const query = queryIndex === -1 ? '' : baseUrl.slice(queryIndex + 1);
95      const params = query
96        ? query.split('&').filter(Boolean).filter((param) => param.split('=')[0] !== 'v')
97        : [];
98      params.push(`v=${encodeURIComponent(normalizedVersion)}`);
99      return `${path}?${params.join('&')}${hash}`;
100    }
101  }
102
103  function safeWidgetUrl(widgetUrl) {
104    widgetUrl = widgetUrl.trim();
105    try {
106      const url = new URL(widgetUrl, window.location.href);
107      return ['http:', 'https:'].includes(url.protocol) ? url.href : '';
108    } catch (error) {
109      if (/^\s*#/.test(widgetUrl) || (/^[a-z][a-z0-9+.-]*:/i.test(widgetUrl) && !/^https?:/i.test(widgetUrl))) {
110        return '';
111      }
112      return widgetUrl;
113    }
114  }
115})(Drupal, drupalSettings);

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.