PageSourceSearch

https://doc.quickrouter.ai/js/main.js?v=80fcf033af262494

js quickrouter.ai collected 2026-10-05 12:28:56 UTC 9,869 bytes, 280 lines download raw bytes

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.