PageSourceSearch

https://trendkraft.io/libraries/custom/base/tk-base-frontpage.js?v=1.0.16

js trendkraft.io collected 2026-09-25 05:11:26 UTC 8,323 bytes, 180 lines download raw bytes

1(function (Drupal, once) {
2  'use strict';
3
4  Drupal.behaviors.tkBaseFrontpage = {
5    attach(context) {
6      once('tk-base-frontpage', '[data-tk-frontpage]', context).forEach((root) => {
7        const focus = root.querySelector('.frontpage-focus');
8        const results = root.querySelector('[data-tk-frontpage-results]');
9        const error = root.querySelector('[data-tk-frontpage-error]');
10        const sortControls = Array.from(root.querySelectorAll('[data-tk-frontpage-sort]'));
11        const typeControls = Array.from(root.querySelectorAll('[data-tk-frontpage-type]'));
12        const filterMenu = root.querySelector('.frontpage-filter-menu');
13        const filterPanel = filterMenu.querySelector('.frontpage-filter-menu__panel');
14        const reset = root.querySelector('[data-tk-frontpage-reset]');
15        const links = Array.from(root.querySelectorAll('[data-tk-frontpage-channel][data-tk-frontpage-fragment]'));
16        const initialHtml = results.innerHTML;
17        let activeLink = reset;
18        let requestController = null;
19        let transition = null;
20
21        const header = document.getElementById('region_header');
22        const navigation = root.querySelector('.frontpage-focus__navigation');
23        const updateOffsets = () => {
24          const headerBottom = header ? header.offsetTop + header.offsetHeight : 0;
25          const hideHeader = activeLink !== reset
26            && window.matchMedia('(min-height: 361px)').matches
27            && focus.getBoundingClientRect().top <= headerBottom;
28          if (header) {
29            header.classList.toggle('frontpage-header--hidden', hideHeader);
30            header.inert = hideHeader;
31          }
32          focus.style.setProperty('--frontpage-header-height', `${hideHeader ? 0 : headerBottom}px`);
33          focus.style.setProperty('--frontpage-viewport-width', `${document.documentElement.clientWidth}px`);
34          if (filterMenu.open) {
35            focus.style.setProperty('--frontpage-filter-top', `${filterPanel.getBoundingClientRect().top}px`);
36          }
37        };
38        updateOffsets();
39        const resizeObserver = new ResizeObserver(updateOffsets);
40        resizeObserver.observe(navigation);
41        if (header) {
42          resizeObserver.observe(header);
43        }
44        let offsetFrame = 0;
45        const scheduleOffsets = () => {
46          if (!offsetFrame) {
47            offsetFrame = window.requestAnimationFrame(() => {
48              offsetFrame = 0;
49              updateOffsets();
50            });
51          }
52        };
53        window.addEventListener('scroll', scheduleOffsets, { passive: true });
54        window.addEventListener('resize', scheduleOffsets);
55        filterMenu.addEventListener('toggle', updateOffsets);
56        navigation.addEventListener('transitionend', updateOffsets);
57
58        const load = async (link, resetOverview = false, focusResult = false) => {
59          requestController?.abort();
60          transition?.cancel();
61          requestController = new AbortController();
62          const controller = requestController;
63          let timedOut = false;
64          const timeout = window.setTimeout(() => {
65            timedOut = true;
66            controller.abort();
67            if (requestController === controller) {
68              transition?.cancel();
69            }
70          }, 10000);
71          const endpoint = new URL(link.dataset.tkFrontpageFragment, window.location.origin);
72          endpoint.searchParams.set('sort', sortControls.find((control) => control.checked).value);
73          endpoint.searchParams.set('type', typeControls.find((control) => control.checked).value);
74          focus.classList.add('is-loading');
75          results.setAttribute('aria-busy', 'true');
76          error.hidden = true;
77
78          try {
79            let html = initialHtml;
80            if (!resetOverview) {
81              const response = await fetch(endpoint, {
82                headers: {
83                  Accept: 'application/json',
84                  'X-Requested-With': 'XMLHttpRequest',
85                },
86                signal: controller.signal,
87              });
88              if (!response.ok) {
89                throw new Error(`Frontpage fragment failed with ${response.status}.`);
90              }
91              const payload = await response.json();
92              html = payload.html;
93            }
94            controller.signal.throwIfAborted();
95            const duration = window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : 180;
96            transition = results.animate([{ opacity: 1 }, { opacity: 0 }], { duration, fill: 'forwards' });
97            await transition.finished;
98            controller.signal.throwIfAborted();
99            results.innerHTML = html;
100            activeLink = link;
101            reset.classList.toggle('is-active', link === reset);
102            links.forEach((item) => item.classList.toggle('is-active', item === link));
103            [reset, ...links].forEach((item) => item.setAttribute('aria-current', item === link ? 'true' : 'false'));
104            if (focusResult) {
105              results.querySelector('.frontpage-section__title a')?.focus({ preventScroll: true });
106            }
107            root.querySelector('[data-tk-frontpage-status]').textContent = [
108              link.textContent.trim(),
109              typeControls.find((control) => control.checked).parentElement.textContent.trim(),
110              sortControls.find((control) => control.checked).parentElement.textContent.trim(),
111            ].join(' · ');
112            updateOffsets();
113            focus.scrollIntoView({ behavior: 'instant', block: 'start' });
114            updateOffsets();
115            transition.cancel();
116            transition = results.animate([{ opacity: 0 }, { opacity: 1 }], { duration, fill: 'forwards' });
117            await transition.finished;
118          }
119          catch (requestError) {
120            if (requestController === controller && (requestError.name !== 'AbortError' || timedOut)) {
121              error.hidden = false;
122            }
123          }
124          finally {
125            window.clearTimeout(timeout);
126            if (requestController === controller) {
127              transition?.cancel();
128              transition = null;
129              focus.classList.remove('is-loading');
130              results.setAttribute('aria-busy', 'false');
131            }
132          }
133        };
134
135        root.addEventListener('click', (event) => {
136          if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
137            return;
138          }
139          const trigger = event.target.closest('[data-tk-frontpage-channel]');
140          const link = links.find((item) => item.dataset.tkFrontpageChannel === trigger?.dataset.tkFrontpageChannel);
141          if (!link) {
142            return;
143          }
144          event.preventDefault();
145          if (trigger !== link && navigation.contains(link)) {
146            link.focus({ preventScroll: true });
147            link.parentElement.scrollLeft = link.offsetLeft - link.parentElement.offsetLeft;
148          }
149          load(link, false, !navigation.contains(link));
150        });
151        reset.addEventListener('click', (event) => {
152          if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
153            return;
154          }
155          event.preventDefault();
156          activeLink = reset;
157          sortControls.find((control) => control.value === 'newest').checked = true;
158          typeControls.find((control) => control.value === 'all').checked = true;
159          load(reset, true);
160        });
161        [...sortControls, ...typeControls].forEach((control) => control.addEventListener('change', () => {
162          filterMenu.open = false;
163          filterMenu.querySelector('summary').focus({ preventScroll: true });
164          load(activeLink);
165        }));
166        filterMenu.addEventListener('keydown', (event) => {
167          if (event.key === 'Escape' && filterMenu.open) {
168            filterMenu.open = false;
169            filterMenu.querySelector('summary').focus({ preventScroll: true });
170          }
171        });
172        document.addEventListener('click', (event) => {
173          if (filterMenu.open && !filterMenu.contains(event.target)) {
174            filterMenu.open = false;
175          }
176        });
177      });
178    },
179  };
180})(Drupal, once);

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.