PageSourceSearch

https://okaapps.com/js/mobile_pages_20260910.js?v=1bb36395cc9a

js okaapps.com collected 2026-10-02 07:19:27 UTC 9,150 bytes, 172 lines download raw bytes

1(function () {
2  'use strict';
3  const phone = window.matchMedia('(max-width: 767px)');
4  let cleanup = () => {};
5
6  function update() {
7    cleanup();
8    cleanup = () => {};
9    if (!phone.matches) return;
10    const undo = [];
11    const chinese = document.body.dataset.mobileLocale === 'zh';
12    const listen = (node, type, fn) => {
13      node.addEventListener(type, fn);
14      undo.push(() => node.removeEventListener(type, fn));
15    };
16    const attribute = (node, name, value) => {
17      const previous = node.getAttribute(name);
18      if (value === null) node.removeAttribute(name); else node.setAttribute(name, value);
19      undo.push(() => previous === null ? node.removeAttribute(name) : node.setAttribute(name, previous));
20    };
21
22    // Internal navigation stays in the same tab on a phone.
23    document.querySelectorAll('a[target="_blank"][href^="/blog/"], a[target="_blank"][href^="/product/"]').forEach(link => attribute(link, 'target', null));
24    document.querySelectorAll('.support p').forEach(paragraph => {
25      Array.from(paragraph.childNodes).filter(node => node.nodeType === Node.TEXT_NODE && node.textContent.trim() === '[email protected]').forEach(node => {
26        const link = document.createElement('a');
27        link.href = 'mailto:[email protected]';
28        link.textContent = node.textContent.trim();
29        node.replaceWith(link);
30        undo.push(() => link.replaceWith(node));
31      });
32    });
33
34    let dialog;
35    let qrTrigger;
36    const closeQR = () => { if (dialog?.open) dialog.close(); };
37    document.querySelectorAll('.support .qrcode-icon, .partners .qrcode-icon').forEach(trigger => {
38      const source = trigger.querySelector('img');
39      if (!source) return;
40      const title = trigger.parentElement.querySelector('h5, p')?.textContent.trim() || (chinese ? '二维码' : 'QR code');
41      attribute(trigger, 'role', 'button');
42      attribute(trigger, 'tabindex', '0');
43      attribute(trigger, 'aria-label', chinese ? `查看${title}二维码` : `Show ${title} QR code`);
44      attribute(trigger, 'aria-haspopup', 'dialog');
45      attribute(trigger, 'aria-expanded', 'false');
46      const openQR = () => {
47        if (!dialog) {
48          dialog = document.createElement('dialog');
49          dialog.className = 'mobile-qr-dialog';
50          dialog.setAttribute('aria-labelledby', 'mobile-qr-title');
51          const close = document.createElement('button');
52          close.type = 'button';
53          close.textContent = '×';
54          close.setAttribute('aria-label', chinese ? '关闭二维码' : 'Close QR code');
55          close.addEventListener('click', closeQR);
56          const heading = document.createElement('h2');
57          heading.id = 'mobile-qr-title';
58          const image = document.createElement('img');
59          const note = document.createElement('p');
60          note.textContent = chinese ? '可长按保存二维码,或使用另一台设备扫码。' : 'Touch and hold to save this QR code, or scan it with another device.';
61          dialog.append(close, heading, image, note);
62          dialog.addEventListener('click', event => {
63            const bounds = dialog.getBoundingClientRect();
64            if (event.target === dialog && (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom)) closeQR();
65          });
66          dialog.addEventListener('close', () => {
67            if (phone.matches) {
68              qrTrigger?.setAttribute('aria-expanded', 'false');
69              qrTrigger?.focus({ preventScroll: true });
70            }
71          });
72          document.body.append(dialog);
73        }
74        qrTrigger = trigger;
75        dialog.querySelector('h2').textContent = title;
76        dialog.querySelector('img').src = source.currentSrc || source.src;
77        dialog.querySelector('img').alt = chinese ? `${title}二维码` : `${title} QR code`;
78        trigger.setAttribute('aria-expanded', 'true');
79        dialog.showModal();
80      };
81      listen(trigger, 'click', event => { event.preventDefault(); openQR(); });
82      listen(trigger, 'keydown', event => {
83        if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); openQR(); }
84      });
85    });
86    undo.push(() => { closeQR(); dialog?.remove(); });
87
88    // Do not advance a slide while someone is reading it on a small screen.
89    const carousel = document.querySelector('#carouselExampleCaptions');
90    if (carousel && window.jQuery?.fn.carousel) {
91      window.jQuery(carousel).carousel('pause');
92      undo.push(() => window.jQuery(carousel).carousel('cycle'));
93      const status = document.createElement('span');
94      status.className = 'mobile-carousel-status';
95      status.setAttribute('aria-live', 'polite');
96      const updateStatus = () => {
97        const slides = [...carousel.querySelectorAll('.carousel-item')];
98        status.textContent = `${slides.findIndex(slide => slide.classList.contains('active')) + 1} / ${slides.length}`;
99      };
100      carousel.append(status);
101      updateStatus();
102      window.jQuery(carousel).on('slid.bs.carousel', updateStatus);
103      undo.push(() => { window.jQuery(carousel).off('slid.bs.carousel', updateStatus); status.remove(); });
104    }
105
106    const reviews = document.querySelector('.comments-div .comment-list');
107    if (reviews) {
108      attribute(reviews, 'tabindex', '0');
109      attribute(reviews, 'aria-label', chinese ? '用户评价,可左右滑动查看' : 'User reviews, scroll horizontally to read more');
110      // Size the horizontal viewport to the visible review, not the longest one.
111      const originalHeight = reviews.style.height;
112      let heightFrame = 0;
113      const fitReview = () => {
114        heightFrame = 0;
115        const left = reviews.getBoundingClientRect().left;
116        const items = [...reviews.querySelectorAll('.comment-item')].filter(item => item.getClientRects().length);
117        const current = items.reduce((closest, item) => !closest || Math.abs(item.getBoundingClientRect().left - left) < Math.abs(closest.getBoundingClientRect().left - left) ? item : closest, null);
118        if (!current) return;
119        const style = getComputedStyle(reviews);
120        const scrollbar = reviews.offsetHeight - reviews.clientHeight;
121        const height = `${Math.ceil(current.getBoundingClientRect().height + parseFloat(style.paddingTop) + parseFloat(style.paddingBottom) + scrollbar)}px`;
122        if (reviews.style.height !== height) reviews.style.height = height;
123      };
124      const scheduleHeight = () => {
125        if (!heightFrame) heightFrame = requestAnimationFrame(fitReview);
126      };
127      listen(reviews, 'scroll', scheduleHeight);
128      listen(window, 'resize', scheduleHeight);
129      const sizeObserver = new ResizeObserver(scheduleHeight);
130      reviews.querySelectorAll('.comment-item').forEach(item => sizeObserver.observe(item));
131      scheduleHeight();
132      undo.push(() => {
133        sizeObserver.disconnect();
134        cancelAnimationFrame(heightFrame);
135        reviews.style.height = originalHeight;
136      });
137    }
138
139    document.querySelectorAll('#android_store_download_link, #android_tv_download_link').forEach(link => listen(link, 'click', event => event.preventDefault()));
140    document.querySelectorAll('.download-overlay').forEach(overlay => {
141      const panel = overlay.querySelector('.download-popup');
142      const close = overlay.querySelector('.download-close-btn');
143      if (!panel || !close) return;
144      attribute(panel, 'role', 'dialog');
145      attribute(panel, 'aria-modal', 'true');
146      attribute(panel, 'aria-label', overlay.querySelector('.download-title')?.textContent.trim() || (chinese ? '下载应用' : 'Download app'));
147      attribute(close, 'aria-label', chinese ? '关闭下载窗口' : 'Close download dialog');
148      let returnFocus;
149      let wasOpen = false;
150      const focusOnChange = () => {
151        const open = overlay.style.display === 'flex';
152        if (open && !wasOpen) { returnFocus = document.activeElement; close.focus({ preventScroll: true }); }
153        if (!open && wasOpen) returnFocus?.focus({ preventScroll: true });
154        wasOpen = open;
155      };
156      const observer = new MutationObserver(focusOnChange);
157      observer.observe(overlay, { attributes: true, attributeFilter: ['style'] });
158      listen(panel, 'keydown', event => {
159        if (event.key === 'Escape') { event.stopPropagation(); close.click(); }
160        if (event.key !== 'Tab') return;
161        const controls = [...panel.querySelectorAll('button, a[href], input, select, textarea, [tabindex="0"]')].filter(node =>
161 node.getClientRects().length);
162        const first = controls[0], last = controls[controls.length - 1];
163        if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
164        else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
165      });
166      undo.push(() => observer.disconnect());
167    });
168    cleanup = () => undo.reverse().forEach(fn => fn());
169  }
170  document.addEventListener('DOMContentLoaded', update, { once: true });
171  phone.addEventListener('change', update);
172})();

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.