WebOrbiton
v1.0.0.1

StocketBase

490 lines · 13.9 KB
  1. (function () {
  2. 'use strict';
  3. ​
  4. var THEME_KEY = 'stocketbase_theme';
  5. ​
  6. function applyTheme(theme) {
  7. document.body.setAttribute('data-theme', theme);
  8. }
  9. ​
  10. function initTheme() {
  11. var stored = localStorage.getItem(THEME_KEY);
  12. if (stored === 'dark' || stored === 'light') {
  13. applyTheme(stored);
  14. return;
  15. }
  16. var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
  17. applyTheme(prefersDark ? 'dark' : 'light');
  18. }
  19. ​
  20. function toggleTheme() {
  21. var current = document.body.getAttribute('data-theme') || 'light';
  22. var next = current === 'dark' ? 'light' : 'dark';
  23. applyTheme(next);
  24. localStorage.setItem(THEME_KEY, next);
  25. }
  26. ​
  27. function initConsent() {
  28. var banner = document.getElementById('consent-banner');
  29. var acceptBtn = document.getElementById('consent-accept');
  30. var declineBtn = document.getElementById('consent-decline');
  31. var consentKey = 'stocketbase_consent';
  32. ​
  33. function runGatedScripts() {
  34. document.querySelectorAll('script.consent-gated-script').forEach(function (oldScript) {
  35. var newScript = document.createElement('script');
  36. Array.from(oldScript.attributes).forEach(function (attr) {
  37. if (attr.name !== 'type') {
  38. newScript.setAttribute(attr.name, attr.value);
  39. }
  40. });
  41. newScript.textContent = oldScript.textContent;
  42. oldScript.parentNode.replaceChild(newScript, oldScript);
  43. });
  44. }
  45. ​
  46. var reopenBtn = document.getElementById('consent-reopen-btn');
  47. var storedConsent = localStorage.getItem(consentKey);
  48. var scriptsRan = false;
  49. ​
  50. function hideBanner() {
  51. if (banner) { banner.style.display = 'none'; }
  52. }
  53. ​
  54. if (storedConsent === 'accepted') {
  55. runGatedScripts();
  56. scriptsRan = true;
  57. }
  58. if (storedConsent) {
  59. hideBanner();
  60. }
  61. ​
  62. if (acceptBtn) {
  63. acceptBtn.addEventListener('click', function () {
  64. localStorage.setItem(consentKey, 'accepted');
  65. if (!scriptsRan) {
  66. runGatedScripts();
  67. scriptsRan = true;
  68. }
  69. hideBanner();
  70. });
  71. }
  72. if (declineBtn) {
  73. declineBtn.addEventListener('click', function () {
  74. localStorage.setItem(consentKey, 'declined');
  75. if (scriptsRan) {
  76. window.location.reload();
  77. return;
  78. }
  79. hideBanner();
  80. });
  81. }
  82. if (reopenBtn && banner) {
  83. reopenBtn.addEventListener('click', function () {
  84. banner.style.display = '';
  85. });
  86. }
  87. }
  88. ​
  89. function initPagination() {
  90. var list = document.getElementById('article-list');
  91. if (!list) {
  92. return;
  93. }
  94. ​
  95. var source = list.dataset.paginationSource;
  96. if (source !== 'home' && source !== 'category' && source !== 'search') {
  97. return;
  98. }
  99. ​
  100. var categorySlug = list.dataset.categorySlug || '';
  101. var searchQuery = list.dataset.searchQuery || '';
  102. var searchCategory = list.dataset.searchCategory || '';
  103. var searchTag = list.dataset.searchTag || '';
  104. var searchSort = list.dataset.searchSort || '';
  105. var loading = false;
  106. ​
  107. function fetchPage(page) {
  108. if (loading) {
  109. return;
  110. }
  111. loading = true;
  112. ​
  113. var url = 'products-fragment.php?source=' + encodeURIComponent(source) + '&page=' + encodeURIComponent(page);
  114. if (source === 'category') {
  115. url += '&slug=' + encodeURIComponent(categorySlug);
  116. }
  117. if (source === 'search') {
  118. url += '&q=' + encodeURIComponent(searchQuery);
  119. if (searchCategory && searchCategory !== '0') {
  120. url += '&category=' + encodeURIComponent(searchCategory);
  121. }
  122. if (searchTag && searchTag !== '0') {
  123. url += '&tag=' + encodeURIComponent(searchTag);
  124. }
  125. if (searchSort) {
  126. url += '&sort=' + encodeURIComponent(searchSort);
  127. }
  128. }
  129. ​
  130. fetch(url)
  131. .then(function (response) {
  132. if (!response.ok) {
  133. throw new Error('Request failed');
  134. }
  135. return response.text();
  136. })
  137. .then(function (html) {
  138. var temp = document.createElement('div');
  139. temp.innerHTML = html;
  140. ​
  141. var newCards = temp.querySelectorAll('.product-card');
  142. newCards.forEach(function (card) {
  143. list.appendChild(card);
  144. });
  145. ​
  146. var container = document.getElementById('pagination-container');
  147. if (container) {
  148. var newControl = temp.querySelector('.pagination-load-more, .pagination-infinite-marker, .pagination-end');
  149. container.innerHTML = '';
  150. if (newControl) {
  151. container.appendChild(newControl);
  152. bindControl(newControl);
  153. }
  154. }
  155. ​
  156. loading = false;
  157. })
  158. .catch(function () {
  159. loading = false;
  160. });
  161. }
  162. ​
  163. function bindControl(control) {
  164. if (!control) {
  165. return;
  166. }
  167. ​
  168. if (control.classList.contains('pagination-load-more')) {
  169. var btn = control.querySelector('.pagination-load-more-btn');
  170. if (btn) {
  171. btn.addEventListener('click', function () {
  172. fetchPage(control.dataset.nextPage);
  173. });
  174. }
  175. return;
  176. }
  177. ​
  178. if (control.classList.contains('pagination-infinite-marker')) {
  179. if (!('IntersectionObserver' in window)) {
  180. return;
  181. }
  182. var observer = new IntersectionObserver(function (entries) {
  183. entries.forEach(function (entry) {
  184. if (entry.isIntersecting) {
  185. observer.disconnect();
  186. fetchPage(control.dataset.nextPage);
  187. }
  188. });
  189. }, { rootMargin: '200px' });
  190. observer.observe(control);
  191. }
  192. }
  193. ​
  194. var container = document.getElementById('pagination-container');
  195. if (container) {
  196. bindControl(container.querySelector('.pagination-load-more, .pagination-infinite-marker'));
  197. }
  198. }
  199. ​
  200. function initBrandShortName() {
  201. var brand = document.querySelector('.site-brand[data-short-name]');
  202. var header = document.querySelector('.site-header');
  203. ​
  204. if (!brand || !header) {
  205. return;
  206. }
  207. ​
  208. function overflows() {
  209. return brand.scrollWidth > brand.clientWidth + 1 || header.scrollWidth > header.clientWidth + 1;
  210. }
  211. ​
  212. function update() {
  213. brand.classList.remove('use-short');
  214. if (overflows()) {
  215. brand.classList.add('use-short');
  216. }
  217. }
  218. ​
  219. update();
  220. window.addEventListener('resize', update);
  221. window.addEventListener('load', update);
  222. }
  223. ​
  224. function initMobileNav() {
  225. var toggle = document.getElementById('site-menu-toggle');
  226. var nav = document.getElementById('site-nav');
  227. var backdrop = document.getElementById('site-nav-backdrop');
  228. var header = document.querySelector('.site-header');
  229. ​
  230. if (!toggle || !nav || !backdrop) {
  231. return;
  232. }
  233. ​
  234. var lockedScrollY = 0;
  235. ​
  236. function openNav() {
  237. nav.classList.add('is-open');
  238. backdrop.classList.add('is-open');
  239. toggle.setAttribute('aria-expanded', 'true');
  240. lockedScrollY = window.scrollY || window.pageYOffset || 0;
  241. document.body.style.position = 'fixed';
  242. document.body.style.top = -lockedScrollY + 'px';
  243. document.body.style.left = '0';
  244. document.body.style.right = '0';
  245. if (header) {
  246. header.classList.add('site-nav-drawer-open');
  247. }
  248. }
  249. ​
  250. function closeNav() {
  251. nav.classList.remove('is-open');
  252. backdrop.classList.remove('is-open');
  253. toggle.setAttribute('aria-expanded', 'false');
  254. document.body.style.position = '';
  255. document.body.style.top = '';
  256. document.body.style.left = '';
  257. document.body.style.right = '';
  258. window.scrollTo(0, lockedScrollY);
  259. if (header) {
  260. header.classList.remove('site-nav-drawer-open');
  261. }
  262. }
  263. ​
  264. toggle.addEventListener('click', function () {
  265. if (nav.classList.contains('is-open')) {
  266. closeNav();
  267. } else {
  268. openNav();
  269. }
  270. });
  271. ​
  272. backdrop.addEventListener('click', closeNav);
  273. ​
  274. nav.querySelectorAll('a').forEach(function (link) {
  275. link.addEventListener('click', closeNav);
  276. });
  277. ​
  278. window.addEventListener('resize', function () {
  279. if (window.innerWidth > 860) {
  280. closeNav();
  281. }
  282. });
  283. }
  284. ​
  285. function initShare() {
  286. var toggle = document.getElementById('share-toggle-btn');
  287. var panel = document.getElementById('share-panel');
  288. var copyBtn = document.getElementById('share-copy-btn');
  289. ​
  290. if (!toggle || !panel) {
  291. return;
  292. }
  293. ​
  294. function closePanel() {
  295. panel.classList.remove('is-open');
  296. toggle.setAttribute('aria-expanded', 'false');
  297. }
  298. ​
  299. function openPanel() {
  300. panel.classList.add('is-open');
  301. toggle.setAttribute('aria-expanded', 'true');
  302. }
  303. ​
  304. toggle.addEventListener('click', function (event) {
  305. event.stopPropagation();
  306. if (panel.classList.contains('is-open')) {
  307. closePanel();
  308. } else {
  309. openPanel();
  310. }
  311. });
  312. ​
  313. document.addEventListener('click', function (event) {
  314. if (panel.classList.contains('is-open') && !panel.contains(event.target) && event.target !== toggle) {
  315. closePanel();
  316. }
  317. });
  318. ​
  319. document.addEventListener('keydown', function (event) {
  320. if (event.key === 'Escape') {
  321. closePanel();
  322. }
  323. });
  324. ​
  325. if (copyBtn) {
  326. copyBtn.addEventListener('click', function () {
  327. var url = copyBtn.getAttribute('data-share-url') || window.location.href;
  328. var label = copyBtn.querySelector('.js-share-copy-label');
  329. var defaultText = copyBtn.getAttribute('data-default-text') || '';
  330. var copiedText = copyBtn.getAttribute('data-copied-text') || '';
  331. ​
  332. function showCopied() {
  333. if (label && copiedText) {
  334. label.textContent = copiedText;
  335. setTimeout(function () {
  336. label.textContent = defaultText;
  337. }, 2000);
  338. }
  339. }
  340. ​
  341. if (navigator.clipboard && navigator.clipboard.writeText) {
  342. navigator.clipboard.writeText(url).then(showCopied);
  343. } else {
  344. var tempInput = document.createElement('input');
  345. tempInput.value = url;
  346. document.body.appendChild(tempInput);
  347. tempInput.select();
  348. document.execCommand('copy');
  349. document.body.removeChild(tempInput);
  350. showCopied();
  351. }
  352. });
  353. }
  354. }
  355. ​
  356. function initAppBanner() {
  357. var banner = document.getElementById('app-banner');
  358. if (!banner) {
  359. return;
  360. }
  361. ​
  362. var DISMISS_KEY = 'stocketbase_app_banner_dismissed';
  363. ​
  364. try {
  365. if (localStorage.getItem(DISMISS_KEY) === '1') {
  366. banner.remove();
  367. return;
  368. }
  369. } catch (e) {}
  370. ​
  371. var userAgent = navigator.userAgent || '';
  372. var isAndroid = /Android/i.test(userAgent);
  373. var isIOS = /iPhone|iPad|iPod/i.test(userAgent);
  374. var androidUrl = banner.getAttribute('data-android-url') || '';
  375. var iosUrl = banner.getAttribute('data-ios-url') || '';
  376. ​
  377. var targetUrl = '';
  378. if (isAndroid && androidUrl) {
  379. targetUrl = androidUrl;
  380. } else if (isIOS && iosUrl) {
  381. targetUrl = iosUrl;
  382. }
  383. ​
  384. if (!targetUrl) {
  385. banner.remove();
  386. return;
  387. }
  388. ​
  389. var link = document.getElementById('app-banner-link');
  390. if (link) {
  391. link.setAttribute('href', targetUrl);
  392. }
  393. ​
  394. banner.classList.add('is-visible');
  395. ​
  396. var closeBtn = document.getElementById('app-banner-close');
  397. if (closeBtn) {
  398. closeBtn.addEventListener('click', function () {
  399. banner.classList.remove('is-visible');
  400. try {
  401. localStorage.setItem(DISMISS_KEY, '1');
  402. } catch (e) {}
  403. });
  404. }
  405. }
  406. ​
  407. function initBackToTop() {
  408. var button = document.getElementById('back-to-top');
  409. if (!button) {
  410. return;
  411. }
  412. ​
  413. var label = button.querySelector('.back-to-top-progress');
  414. var content = button.getAttribute('data-progress') === '1' ? document.querySelector('.article-content') : null;
  415. var template = button.getAttribute('data-left-text') || '{percent}%';
  416. var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  417. var ticking = false;
  418. ​
  419. if (content && label) {
  420. button.classList.add('has-progress');
  421. label.hidden = false;
  422. }
  423. ​
  424. function update() {
  425. ticking = false;
  426. ​
  427. var visible = (window.pageYOffset || 0) > 400;
  428. button.classList.toggle('is-visible', visible);
  429. button.tabIndex = visible ? 0 : -1;
  430. ​
  431. if (content && label) {
  432. var rect = content.getBoundingClientRect();
  433. var read = rect.height > 0 ? Math.min(1, Math.max(0, (window.innerHeight - rect.top) / rect.height)) : 1;
  434. var percentRead = Math.round(read * 100);
  435. label.textContent = template.replace('{percent}', String(100 - percentRead));
  436. button.style.setProperty('--btt-read', percentRead + '%');
  437. }
  438. }
  439. ​
  440. function schedule() {
  441. if (!ticking) {
  442. ticking = true;
  443. requestAnimationFrame(update);
  444. }
  445. }
  446. ​
  447. window.addEventListener('scroll', schedule, { passive: true });
  448. window.addEventListener('resize', schedule);
  449. button.addEventListener('click', function () {
  450. window.scrollTo({ top: 0, behavior: reduceMotion ? 'auto' : 'smooth' });
  451. });
  452. update();
  453. }
  454. ​
  455. document.addEventListener('DOMContentLoaded', function () {
  456. initTheme();
  457. initBackToTop();
  458. var toggleBtn = document.getElementById('theme-toggle-btn');
  459. if (toggleBtn) {
  460. toggleBtn.addEventListener('click', toggleTheme);
  461. }
  462. initConsent();
  463. initPagination();
  464. initMobileNav();
  465. initBrandShortName();
  466. initShare();
  467. initAppBanner();
  468. });
  469. })();
  470. ​
  471. (function () {
  472. var switcher = document.querySelector('[data-language-switcher]');
  473. if (!switcher) {
  474. return;
  475. }
  476. ​
  477. document.addEventListener('click', function (event) {
  478. if (switcher.open && !switcher.contains(event.target)) {
  479. switcher.open = false;
  480. }
  481. });
  482. ​
  483. document.addEventListener('keydown', function (event) {
  484. if (event.key === 'Escape' && switcher.open) {
  485. switcher.open = false;
  486. switcher.querySelector('summary').focus();
  487. }
  488. });
  489. })();
  490. ​