1/* docs/components/sidebar/sidebar-nav.js 2 * Sidebar deep-link navigation: 3 * - ensure section is visible (via showSection) 4 * - scroll to a target element inside the section 5 * - flash/outline the emphasized element (the "line" the user lands on) 6 */ 7(function () { 8 'use strict'; 9 10 const LOG_PREFIX = '[sidebar-nav]'; 11 12 function getHeaderHeightPx() { 13 const header = 14 document.querySelector('.site-header') || 15 document.querySelector('#site-header .site-header') || 16 document.querySelector('nav.navbar'); 17 18 const cssVar = getComputedStyle(document.documentElement) 19 .getPropertyValue('--header-h') 20 .trim(); 21 22 const fallback = Number.parseInt(cssVar || '70', 10) || 70; 23 const measured = header?.getBoundingClientRect().height || 0; 24 25 return Math.max(fallback, measured || 0); 26 } 27 28 function scrollToWithHeaderOffset(targetEl, extra = 12) { 29 if (!targetEl) return; 30 31 const header = 32 document.querySelector('.site-header') || 33 document.querySelector('#site-header .site-header') || 34 document.querySelector('nav.navbar'); 35 36 const headerH = 37 header?.getBoundingClientRect().height || 38 parseInt(getComputedStyle(document.documentElement).getPropertyValue('--header-h') || '70', 10) || 39 70; 40 41 const bannerH = 42 parseInt(getComputedStyle(document.documentElement).getPropertyValue('--cohort-banner-offset') || '0', 10) || 43 0; 44 45 const topChrome = headerH + bannerH; 46 47 const y = window.scrollY + targetEl.getBoundingClientRect().top - topChrome - extra; 48 window.scrollTo({ top: Math.max(0, y), behavior: 'smooth' }); 49 } 50 function getTopChromeHeightPx() { 51 const header = 52 document.querySelector('.site-header') || 53 document.querySelector('#site-header .site-header') || 54 document.querySelector('nav.navbar'); 55 56 const cssHeaderVar = getComputedStyle(document.documentElement) 57 .getPropertyValue('--header-h') 58 .trim(); 59 60 const headerFallback = Number.parseInt(cssHeaderVar || '70', 10) || 70; 61 const headerMeasured = header?.getBoundingClientRect().height || 0; 62 const headerH = Math.max(headerFallback, headerMeasured || 0); 63 64 const bannerVar = getComputedStyle(document.documentElement) 65 .getPropertyValue('--cohort-banner-offset') 66 .trim(); 67 68 const bannerH = Number.parseInt(bannerVar || '0', 10) || 0; 69 70 return headerH + bannerH; 71 } 72 function openAnyDetailsAncestors(el) { 73 // If target is inside <details>, open them so the user actually sees it. 74 let node = el?.parentElement; 75 while (node) { 76 if (node.tagName === 'DETAILS' && !node.open) node.open = true; 77 node = node.parentElement; 78 } 79 } 80 81 function flashEmphasis(el, ms = 1600) { 82 if (!el) return; 83 84 openAnyDetailsAncestors(el); 85 86 // Remove then re-add so repeated clicks retrigger animation reliably 87 el.classList.remove('haiphen-nav-flash'); 88 // force reflow 89 void el.offsetWidth; 90 el.classList.add('haiphen-nav-flash'); 91 92 window.setTimeout(() => { 93 el.classList.remove('haiphen-nav-flash'); 94 }, ms); 95 } 96 97 function waitForElement(root, selector, timeoutMs = 2500) { 98 return new Promise((resolve) => { 99 if (!root) return resolve(null); 100 const existing = root.querySelector(selector); 101 if (existing) return resolve(existing); 102 103 const obs = new MutationObserver(() => { 104 const found = root.querySelector(selector); 105 if (found) { 106 obs.disconnect(); 107 resolve(found); 108 } 109 }); 110 111 obs.observe(root, { childList: true, subtree: true }); 112 113 window.setTimeout(() => { 114 obs.disconnect(); 115 resolve(root.querySelector(selector) || null); 116 }, timeoutMs); 117 }); 118 } 119 120 async function navigate({ section, target, emphasize, extraOffset = 12 }) { 121 const contentRoot = document.getElementById('content-widget'); 122 123 if (!section || typeof window.showSection !== 'function') { 124 console.warn(`${LOG_PREFIX} missing section or showSection()`, { section }); 125 return; 126 } 127 128 // 2) Wait for target to exist (content is injected dynamically) 129 // Prefer emphasize selector; fallback to target selector; fallback to contentRoot. 130 const pickSelector = emphasize || target; 131 132 // 1) Ensure the section is rendered + URL updated 133 // Use navigateTo for path-based URLs; fall back to showSection 134 if (typeof window.navigateTo === 'function' && !pickSelector) { 135 window.navigateTo(section); 136 } else { 137 window.showSection(section, pickSelector ? { suppressScroll: true } : undefined); 138 // Update URL path even when using showSection for scroll control 139 if (typeof window.navigateTo === 'function') { 140 const SECTION_PATH = { Trades: '/signals', Services: '/services', OnePager: '/
140platform', FAQ: '/faq', Contact: '/contact', Docs: '/docs', GettingStarted: '/getting-started', Profile: '/profile' }; 141 const p = SECTION_PATH[section]; 142 if (p) history.replaceState({ section: section }, '', p); 143 } 144 } 145 let el = null; 146 147 if (pickSelector) { 148 el = await waitForElement(contentRoot, pickSelector); 149 } 150 151 const fallbackTarget = el || (target ? contentRoot?.querySelector(target) : null) || contentRoot; 152 if (!fallbackTarget) return; 153 154 // 3) Scroll to it with header offset 155 // Delay one frame so layout settles after showSection's own scroll 156 requestAnimationFrame(() => { 157 scrollToWithHeaderOffset(fallbackTarget, extraOffset); 158 // 4) Flash the emphasized "line" 159 flashEmphasis(el || fallbackTarget); 160 }); 161 } 162 163 // Expose on your existing namespace 164 window.HAIPHEN = window.HAIPHEN || {}; 165 window.HAIPHEN.sidebarNavigate = navigate; 166})();
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.