PageSourceSearch

https://dniprorada.gov.ua/content/app/js/highlight.js?v=639264619174541886

js dniprorada.gov.ua collected 2026-10-01 11:32:32 UTC 10,147 bytes, 313 lines download raw bytes

1(function () {
2        function getHighlightText() {
3            const params = new URLSearchParams(window.location.search);
4            const value = params.get('highlight');
5
6            if (!value) {
7                return '';
8            }
9
10            // URLSearchParams уже декодирует значение, но оставляем fallback на случай двойного кодирования.
11            try {
12                return decodeURIComponent(value).trim();
13            } catch (error) {
14                return value.trim();
15            }
16        }
17
18        function escapeRegExp(value) {
19            return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
20        }
21
22        function createTermPattern(term) {
23            const chars = Array.from(term);
24            const minPrefixLength = Math.min(chars.length, 4);
25            const prefixes = [];
26
27            for (let length = chars.length; length >= minPrefixLength; length -= 1) {
28                prefixes.push(`${escapeRegExp(chars.slice(0, length).join(''))}[\\p{L}\\p{M}\\p{N}_'’ʼ-]*`);
29            }
30
31            return `(?:${prefixes.join('|')})`;
32        }
33
34        function createHighlightRegex(text) {
35            const normalized = text.trim();
36            if (!normalized) {
37                return null;
38            }
39
40            const patterns = normalized
41                .split(/\s+/u)
42                .filter(Boolean)
43                .map((term) => createTermPattern(term));
44
45            const phraseSeparator = '[\\s\\u00a0]+';
46            const phrasePattern = patterns.join(phraseSeparator);
47
48            return phrasePattern ? new RegExp(phrasePattern, 'giu') : null;
49        }
50
51        function shouldSkipNode(node) {
52            const parent = node.parentElement;
53            if (!parent) {
54                return true;
55            }
56
57            return Boolean(parent.closest('script, style, noscript, textarea, mark[data-query-highlight="1"]'));
58        }
59
60        function collectTextNodes(root) {
61            const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
62                acceptNode(node) {
63                    if (!node.nodeValue || !node.nodeValue.trim()) {
64                        return NodeFilter.FILTER_REJECT;
65                    }
66
67                    if (shouldSkipNode(node)) {
68                        return NodeFilter.FILTER_REJECT;
69                    }
70
71                    return NodeFilter.FILTER_ACCEPT;
72                }
73            });
74
75            const nodes = [];
76            let currentNode = walker.nextNode();
77            while (currentNode) {
78                nodes.push(currentNode);
79                currentNode = walker.nextNode();
80            }
81
82            return nodes;
83        }
84
85        function highlightTextNode(node, regex) {
86            const text = node.nodeValue;
87            if (!text) {
88                return 0;
89            }
90
91            regex.lastIndex = 0;
92            if (!regex.test(text)) {
93                return 0;
94            }
95
96            regex.lastIndex = 0;
97            const fragment = document.createDocumentFragment();
98            let lastIndex = 0;
99            let count = 0;
100            let match;
101
102            while ((match = regex.exec(text)) !== null) {
103                const matchIndex = match.index;
104                const matchText = match[0];
105
106                if (matchIndex > lastIndex) {
107                    fragment.appendChild(document.createTextNode(text.slice(lastIndex, matchIndex)));
108                }
109
110                const mark = document.createElement('mark');
111                mark.dataset.queryHighlight = '1';
112                mark.style.backgroundColor = '#ffeb3b';
113                mark.style.padding = '0';
114                mark.textContent = matchText;
115                fragment.appendChild(mark);
116
117                lastIndex = matchIndex + matchText.length;
118                count += 1;
119
120                if (matchText.length === 0) {
121                    regex.lastIndex += 1;
122                }
123            }
124
125            if (lastIndex < text.length) {
126                fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
127            }
128
129            node.parentNode.replaceChild(fragment, node);
130            return count;
131        }
132
133        function highlightMatchingContainers() {
134            const marks = document.querySelectorAll('mark[data-query-highlight="1"]');
135            const containers = new Set();
136            const containerSelector = 'p, li, div, td, th, section, article, blockquote, h1, h2, h3, h4, h5, h6, tr';
137
138            for (const mark of marks) {
139                const container = mark.closest(containerSelector);
140                if (container) {
141                    containers.add(container);
142                }
143            }
144
145            for (const container of containers) {
146                if (container.dataset.queryHighlightContainer === '1') {
147                    continue;
148                }
149
150                container.dataset.queryHighlightContainer = '1';
151                container.style.backgroundColor = '#fffde7';
152            }
153        }
154
155        function isCollapseExpanded(element) {
156            return element.classList.contains('in') || element.classList.contains('show');
157        }
158
159        function getParentCollapses(element) {
160            const collapses = [];
161            let parent = element.parentElement;
162
163            while (parent) {
164                if (parent.classList && parent.classList.contains('collapse')) {
165                    collapses.unshift(parent);
166                }
167
168                parent = parent.parentElement;
169            }
170
171            return collapses;
172        }
173
174        function isCollapseTriggerForElement(trigger, target) {
175            const href = trigger.getAttribute('href');
176            const dataTarget = trigger.getAttribute('data-target');
177
178            return href === target || dataTarget === target || Boolean(href && href.endsWith(target));
179        }
180
181        function setCollapseTriggerExpanded(element) {
182            if (!element.id) {
183                return;
184            }
185
186            const target = `#${element.id}`;
187            const triggers = document.querySelectorAll('[data-toggle="collapse"]');
188
189            for (const trigger of triggers) {
190                if (!isCollapseTriggerForElement(trigger, target)) {
191                    continue;
192                }
193
194                trigger.classList.remove('collapsed');
195                trigger.setAttribute('aria-expanded', 'true');
196
197                const title = trigger.closest('.title');
198                const icon = title ? title.querySelector('.fa-angle-down') : null;
199
200                if (icon) {
201                    icon.classList.add('rotate-180');
202                }
203            }
204        }
205
206        function getJQueryCollapseElement(element) {
207            if (!window.jQuery) {
208                return null;
209            }
210
211            const jqueryElement = window.jQuery(element);
212            if (typeof jqueryElement.collapse !== 'function') {
213                return null;
214            }
215
216            return jqueryElement;
217        }
218
219        function showCollapse(element) {
220            setCollapseTriggerExpanded(element);
221
222            if (isCollapseExpanded(element)) {
223                return Promise.resolve();
224            }
225
226            return new Promise((resolve) => {
227                let isResolved = false;
228                let timeoutId = null;
229                const jqueryElement = getJQueryCollapseElement(element);
230
231                const resolveOnce = () => {
232                    if (isResolved) {
233                        return;
234                    }
235
236                    isResolved = true;
237
238                    if (timeoutId) {
239                        window.clearTimeout(timeoutId);
240                    }
241
242                    if (jqueryElement) {
243                        jqueryElement.off('shown.bs.collapse', resolveOnce);
244                    }
245
246                    resolve();
247                };
248
249                if (jqueryElement) {
250                    jqueryElement.one('shown.bs.collapse', resolveOnce);
251                    jqueryElement.collapse('show');
252                    timeoutId = window.setTimeout(resolveOnce, 700);
253                    return;
254                }
255
256                element.classList.add('in');
257                element.classList.add('show');
258                element.style.display = 'block';
259                element.style.height = 'auto';
260
261                resolveOnce();
262            });
263        }
264
265        async function openParentCollapses(element) {
266            const collapses = getParentCollapses(element);
267
268            for (const collapse of collapses) {
269                await showCollapse(collapse);
270            }
271        }
272
273        async function runHighlight() {
274            const query = getHighlightText();
275            const regex = createHighlightRegex(query);
276
277            if (!regex) {
278                return;
279            }
280
281            const contentBlocks = document.querySelectorAll('.page-content-block');
282            if (!contentBlocks.length) {
283                return;
284            }
285
286            const textNodes = [];
287            for (const block of contentBlocks) {
288                textNodes.push(...collectTextNodes(block));
289            }
290
291            let highlightedCount = 0;
292
293            for (const node of textNodes) {
294                highlightedCount += highlightTextNode(node, regex);
295            }
296
297            if (highlightedCount > 0) {
298                highlightMatchingContainers();
299
300                const firstMatch = document.querySelector('mark[data-query-highlight="1"]');
301                if (firstMatch) {
302                    await openParentCollapses(firstMatch);
303                    firstMatch.scrollIntoView({behavior: 'smooth', block: 'center'});
304                }
305            }
306        }
307
308        if (document.readyState === 'loading') {
309            document.addEventListener('DOMContentLoaded', runHighlight);
310        } else {
311            runHighlight();
312        }
313    })();

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.