1(function() { 2 'use strict'; 3 4 // Sidebar toggle 5 var toggle = document.getElementById('sidebarToggle'); 6 var sidebar = document.getElementById('sidebar'); 7 var closeBtn = document.getElementById('sidebarClose'); 8 9 function createOverlay() { 10 var overlay = document.createElement('div'); 11 overlay.className = 'sidebar-overlay'; 12 overlay.id = 'sidebarOverlay'; 13 var layout = document.querySelector('.layout'); 14 if (layout) { 15 layout.appendChild(overlay); 16 } else { 17 document.body.appendChild(overlay); 18 } 19 overlay.addEventListener('click', closeSidebar); 20 return overlay; 21 } 22 23 function openSidebar() { 24 sidebar.classList.add('open'); 25 var overlay = document.getElementById('sidebarOverlay') || createOverlay(); 26 overlay.classList.add('open'); 27 document.body.style.overflow = 'hidden'; 28 } 29 30 function closeSidebar() { 31 sidebar.classList.remove('open'); 32 var overlay = document.getElementById('sidebarOverlay'); 33 if (overlay) overlay.classList.remove('open'); 34 document.body.style.overflow = ''; 35 } 36 37 if (toggle) toggle.addEventListener('click', openSidebar); 38 if (closeBtn) closeBtn.addEventListener('click', closeSidebar); 39 40 // Sidebar section collapse 41 var lastToggleTime = 0; 42 var sidebar = document.getElementById('sidebar'); 43 if (sidebar) { 44 sidebar.addEventListener('click', function(e) { 45 var title = e.target.closest('.nav-section-title'); 46 if (!title) return; 47 if (e.target.tagName === 'A') return; 48 var now = Date.now(); 49 if (now - lastToggleTime < 400) return; 50 lastToggleTime = now; 51 var section = title.parentElement; 52 if (section.classList.contains('nav-section')) { 53 section.classList.toggle('collapsed'); 54 } 55 }); 56 } 57 58 // Copy buttons on code blocks 59 document.querySelectorAll('pre').forEach(function(pre) { 60 var btn = document.createElement('button'); 61 btn.className = 'copy-btn'; 62 btn.textContent = 'å¤å¶'; 63 btn.addEventListener('click', function() { 64 var code = pre.querySelector('code'); 65 var text = code ? code.textContent : pre.textContent; 66 if (navigator.clipboard) { 67 navigator.clipboard.writeText(text).then(function() { 68 btn.textContent = 'å·²å¤å¶'; 69 btn.classList.add('copied'); 70 setTimeout(function() { 71 btn.textContent = 'å¤å¶'; 72 btn.classList.remove('copied'); 73 }, 2000); 74 }); 75 } else { 76 var textarea = document.createElement('textarea'); 77 textarea.value = text; 78 textarea.style.position = 'fixed'; 79 textarea.style.opacity = '0'; 80 document.body.appendChild(textarea); 81 textarea.select(); 82 document.execCommand('copy'); 83 document.body.removeChild(textarea); 84 btn.textContent = 'å·²å¤å¶'; 85 btn.classList.add('copied'); 86 setTimeout(function() { 87 btn.textContent = 'å¤å¶'; 88 btn.classList.remove('copied'); 89 }, 2000); 90 } 91 }); 92 pre.style.position = 'relative'; 93 pre.appendChild(btn); 94 }); 95 96 // Auto-expand active section in sidebar 97 var activeItem = document.querySelector('.nav-item.active'); 98 if (activeItem) { 99 var parent = activeItem.parentElement; 100 while (parent) { 101 if (parent.classList && parent.classList.contains('nav-section')) { 102 parent.classList.remove('collapsed'); 103 } 104 parent = parent.parentElement; 105 } 106 // Scroll active item into view 107 setTimeout(function() { 108 activeItem.scrollIntoView({ block: 'center', behavior: 'smooth' }); 109 }, 100); 110 } 111 112 // iOS rubber banding fix for sidebar 113 (function() { 114 var sidebar = document.getElementById('sidebar'); 115 if (!sidebar) return; 116 var isScrolling = false; 117 var startY = 0; 118 var startScrollTop = 0; 119 120 sidebar.addEventListener('touchstart', function(e) { 121 isScrolling = true; 122 startY = e.touches[0].pageY; 123 startScrollTop = sidebar.scrollTop; 124 }, { passive: true }); 125 126 sidebar.addEventListener('touchmove', function(e) { 127 if (!isScrolling) return; 128 var currentY = e.touches[0].pageY; 129 var deltaY = currentY - startY; 130 var scrollTop = sidebar.scrollTop; 131 var scrollHeight = sidebar.scrollHeight; 132 var clientHeight = sidebar.clientHeight; 133 134 // At top and trying to pull down 135 if (scrollTop === 0 && deltaY > 0) { 136 e.preventDefault(); 137 return; 138 } 139 140 // At bottom and trying to pull up 141 var atBottom = scrollTop + clientHeight >= scrollHeight - 1; 142 if (atBottom && deltaY < 0) { 143 e.preventDefault(); 144 return; 145 } 146 }, { passive: false }); 147 148 sidebar.addEventListener('touchend', function() { 149 isScrolling = false;
150 }, { passive: true }); 151 })(); 152 153 // Theme toggle 154 var themeToggle = document.getElementById('themeToggle'); 155 var savedTheme; 156 try { savedTheme = localStorage.getItem('qr-theme'); } catch (_) {} 157 document.documentElement.setAttribute('data-theme', savedTheme || (document.body.classList.contains('reader-page') ? 'light' : 'dark')); 158 if (themeToggle) { 159 themeToggle.addEventListener('click', function() { 160 var current = document.documentElement.getAttribute('data-theme') || 'light'; 161 if (current === 'light') { 162 document.documentElement.setAttribute('data-theme', 'dark'); 163 localStorage.setItem('qr-theme', 'dark'); 164 } else { 165 document.documentElement.setAttribute('data-theme', 'light'); 166 localStorage.setItem('qr-theme', 'light'); 167 } 168 }); 169 } 170 171 // Code language tabs 172 document.querySelectorAll('.code-tabs').forEach(function(tabsContainer) { 173 var tabs = tabsContainer.querySelectorAll('.code-tab'); 174 var panels = tabsContainer.querySelectorAll('.code-tab-panel');
175 tabs.forEach(function(tab) { 176 tab.addEventListener('click', function() { 177 tabs.forEach(function(t) { t.classList.remove('active'); }); 178 panels.forEach(function(p) { p.classList.remove('active'); }); 179 tab.classList.add('active'); 180 var target = tab.getAttribute('data-tab'); 181 var panel = tabsContainer.querySelector('[data-panel="' + target + '"]'); 182 if (panel) panel.classList.add('active'); 183 }); 184 }); 185 }); 186 187 // === Syntax highlighting (highlight.js, VS Code-style) === 188 var LANG_ALIASES = { 189 'shell': 'bash', 'sh': 'bash', 'console': 'bash', 'terminal': 'bash', 190 'js': 'javascript', 'ts': 'typescript', 'py': 'python', 191 'objective': 'objectivec', 'obj-c': 'objectivec', 192 'csharp': 'csharp', 'cs': 'csharp' 193 }; 194 var LANG_LABEL = { 195 json: 'JSON', bash: 'Shell', shell: 'Shell', javascript: 'JavaScript', 196 js: 'JavaScript', typescript: 'TypeScript', ts: 'TypeScript', python: 'Python', 197 py: 'Python', java: 'Java', go: 'Go', php: 'PHP', ruby: 'Ruby', 198 swift: 'Swift', dart: 'Dart', http: 'HTTP', r: 'R', ocaml: 'OCaml', 199 csharp: 'C#', cs: 'C#', c: 'C', cpp: 'C++', 'c++': 'C++', 200 objectivec: 'Obj-C', html: 'HTML', css: 'CSS', sql: 'SQL', yaml: 'YAML', 201 xml: 'XML', plaintext: 'Text', text: 'Text' 202 }; 203 204 function getLang(codeEl) { 205 var cls = codeEl.className || ''; 206 var m = cls.match(/language-([\w+-]+)/); 207 if (!m) return ''; 208 var lang = m[1].toLowerCase(); 209 return LANG_ALIASES[lang] || lang; 210 } 211 212 function applyHighlight(codeEl) { 213 if (codeEl.getAttribute('data-hl') === 'done') return; 214 if (typeof hljs === 'undefined') { return; } 215 var lang = getLang(codeEl); 216 try { 217 if (lang && hljs.getLanguage(lang)) { 218 var res = hljs.highlight(codeEl.textContent, { language: lang, ignoreIllegals: true }); 219 codeEl.innerHTML = res.value; 220 codeEl.classList.add('hljs', 'language-' + lang); 221 } else { 222 var res2 = hljs.highlightAuto(codeEl.textContent); 223 codeEl.innerHTML = res2.value; 224 codeEl.classList.add('hljs'); 225 } 226 } catch (e) { 227 // leave as-is on any error; never break the page 228 } 229 codeEl.setAttribute('data-hl', 'done'); 230 } 231 232 function decoratePre(pre) { 233 if (pre.getAttribute('data-decorated') === '1') return; 234 var code = pre.querySelector('code'); 235 if (!code) return; 236 applyHighlight(code); 237 238 // language label chip (top-left) + ensure copy button sits top-right 239 var m = (code.className || '').match(/language-([\w+-]+)/); 240 var langKey = m ? m[1].toLowerCase() : ''; 241 var resolved = getLang(code); 242 var label = LANG_LABEL[langKey] || LANG_LABEL[resolved] || (langKey ? langKey.toUpperCase() : ''); 243 if (label) { 244 var chip = document.createElement('span'); 245 chip.className = 'code-lang-chip'; 246 chip.textContent = label; 247 pre.appendChild(chip); 248 } 249 pre.setAttribute('data-decorated', '1'); 250 } 251 252 function highlightAll(scope) { 253 var root = scope || document.querySelector('article.doc-content') || document; 254 root.querySelectorAll('pre > code').forEach(function(code) { 255 decoratePre(code.parentElement); 256 }); 257 } 258 259 // initial highlight (covers the visible/active panel) 260 highlightAll(); 261 // also highlight inside hidden code-tab panels so they're ready on switch 262 document.querySelectorAll('.code-tab-panel').forEach(function(panel) { 263 panel.querySelectorAll('pre > code').forEach(function(code) { decoratePre(code.parentElement); }); 264 }); 265 266 267})(); 268 269// OS tab switching for tutorial pages 270function switchOS(btn, os) { 271 var container = btn.closest('.doc-content') || btn.parentElement.parentElement; 272 // Deactivate all tabs and panels 273 var tabs = btn.parentElement.querySelectorAll('.os-tab');
274 tabs.forEach(function(t) { t.classList.remove('active'); }); 275 btn.classList.add('active'); 276 var panels = container.querySelectorAll('.os-panel'); 277 panels.forEach(function(p) { p.classList.remove('active'); }); 278 var target = document.getElementById('os-' + os); 279 if (target) target.classList.add('active'); 280}
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.