page_utils.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. // doc/page_utils.js
  2. // Small helpers shared by documentation demo pages (3rd column).
  3. (function () {
  4. function qs(sel) { return document.querySelector(sel); }
  5. function qsa(sel) { return Array.from(document.querySelectorAll(sel)); }
  6. function showToast(msg) {
  7. const el = document.getElementById('toast');
  8. if (!el) return;
  9. el.textContent = msg;
  10. el.classList.add('show');
  11. clearTimeout(el._t);
  12. el._t = setTimeout(() => el.classList.remove('show'), 900);
  13. }
  14. async function copyActiveCode() {
  15. const active = qs('.code-view.active, .html-view.active, .css-view.active');
  16. const text = active ? active.innerText.trimEnd() : '';
  17. if (!text) return;
  18. try {
  19. await navigator.clipboard.writeText(text);
  20. showToast('Copied');
  21. } catch (e) {
  22. // Fallback
  23. const ta = document.createElement('textarea');
  24. ta.value = text;
  25. ta.style.position = 'fixed';
  26. ta.style.opacity = '0';
  27. document.body.appendChild(ta);
  28. ta.select();
  29. document.execCommand('copy');
  30. ta.remove();
  31. showToast('Copied');
  32. }
  33. }
  34. function switchTab(tab) {
  35. const tabs = qsa('.tabs .tab');
  36. tabs.forEach(t => t.classList.remove('active'));
  37. qsa('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
  38. const t = String(tab || 'js').toLowerCase();
  39. const idx = (t === 'js') ? 0 : (t === 'html') ? 1 : 2;
  40. const tabEls = [tabs[0], tabs[1], tabs[2]];
  41. const viewEls = [
  42. document.getElementById('js-code'),
  43. document.getElementById('html-code'),
  44. document.getElementById('css-code')
  45. ];
  46. tabEls.forEach((el, i) => {
  47. if (!el) return;
  48. const active = i === idx;
  49. el.classList.toggle('active', active);
  50. el.setAttribute('aria-selected', active ? 'true' : 'false');
  51. el.tabIndex = active ? 0 : -1;
  52. });
  53. const view = viewEls[idx];
  54. if (view) view.classList.add('active');
  55. }
  56. function enableTabKeyboardNav() {
  57. document.addEventListener('keydown', (e) => {
  58. const focused = document.activeElement;
  59. if (!focused || !focused.classList.contains('tab')) return;
  60. if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
  61. e.preventDefault();
  62. const tabs = qsa('.tabs .tab');
  63. const idx = Math.max(0, tabs.indexOf(focused));
  64. const nextIdx = (e.key === 'ArrowRight')
  65. ? (idx + 1) % tabs.length
  66. : (idx - 1 + tabs.length) % tabs.length;
  67. const next = tabs[nextIdx];
  68. const label = (next?.textContent || '').toLowerCase();
  69. const lang = label.includes('html') ? 'html' : label.includes('css') ? 'css' : 'js';
  70. switchTab(lang);
  71. qs('.tab.active')?.focus();
  72. });
  73. }
  74. function syncPrevNext(currentUrl) {
  75. try {
  76. const pn = window.parent?.docGetPrevNext?.(currentUrl);
  77. const prevEl = document.getElementById('prev-title');
  78. const nextEl = document.getElementById('next-title');
  79. const centerEl = document.getElementById('nav-center');
  80. if (centerEl) centerEl.textContent = pn?.current?.group || centerEl.textContent || '';
  81. if (prevEl) prevEl.textContent = pn?.prev?.title || '—';
  82. if (nextEl) nextEl.textContent = pn?.next?.title || '—';
  83. } catch {}
  84. }
  85. function syncMiddleActive(currentUrl) {
  86. try { window.parent?.setMiddleActive?.(currentUrl); } catch {}
  87. }
  88. // Expose
  89. window.DocPage = {
  90. showToast,
  91. copyActiveCode,
  92. switchTab,
  93. enableTabKeyboardNav,
  94. syncPrevNext,
  95. syncMiddleActive
  96. };
  97. }());