(function () { function decode(value) { try { return decodeURIComponent(value); } catch (error) { return value; } } function encodedRoute(path) { return path.split('/').map(encodeURIComponent).join('/'); } function configureImages(root, homepage) { if (!root) return; root.querySelectorAll('img').forEach(function (img) { if (homepage && img.classList.contains('site-logo')) { img.loading = 'eager'; img.setAttribute('fetchpriority', 'high'); } else if (!img.hasAttribute('loading')) { img.loading = 'lazy'; } img.decoding = 'async'; }); } function preserveLegacyLinks() { var hash = location.hash; var legacy = (window.CS_NOTES_LEGACY_ROUTES || {})[decode(location.pathname)]; if (legacy) { var heading = hash && !hash.startsWith('#/') ? '?id=' + encodeURIComponent(decode(hash.slice(1))) : ''; location.replace('/#' + encodedRoute(legacy) + heading); return; } var route = decode(hash.split('?')[0].replace(/^#\//, '').replace(/\.md$/, '')); if (route && !route.includes('/') && window.CS_NOTES_PATHS.includes('/notes/' + route)) { location.replace('#/notes/' + encodeURIComponent(route) + (hash.includes('?') ? '?' + hash.split('?').slice(1).join('?') : '')); } } var stopSpy = null; // 高频小节:构建网站时把标题和目录中的 🔥 换成了空的 span(hot-1 为一个火焰,hot-2 为两个),这里画出小火焰,侧边栏中也显示同样的标记 var FLAME = ''; function hotLevel(node) { return node.classList.contains('hot-2') ? 2 : 1; } function fillHot(node, level) { node.title = level === 2 ? '面试中最常问' : '面试中常被问到'; node.setAttribute('role', 'img'); node.setAttribute('aria-label', level === 2 ? '高频' : '常问'); node.innerHTML = level === 2 ? FLAME + FLAME : FLAME; } function hotBadge(level) { var badge = document.createElement('span'); badge.className = 'hot hot-' + level; fillHot(badge, level); return badge; } function drawHot(section) { section.querySelectorAll('.hot:empty').forEach(function (node) { fillHot(node, hotLevel(node)); }); } // 步骤动图:GitHub 上直接播放 GIF;网站上用同一个 GIF 的各帧(构建时拆出)做播放器,可以暂停、逐步查看 function stepAnimations(section) { var map = window.CS_NOTES_ANIMATIONS || {}; section.querySelectorAll('img').forEach(function (img) { if (img.dataset.anim) return; var key; try { key = decodeURI(new URL(img.getAttribute('src'), location.href).pathname); } catch (error) { return; } var anim = map[key]; if (!anim) return; img.dataset.anim = '1'; anim.frames.forEach(function (src) { new Image().src = src; }); var index = 0, timer = null, playing = false, visible = false; var bar = document.createElement('div'); bar.className = 'anim-bar'; bar.innerHTML = '' + '' + ''; var playButton = bar.querySelector('[data-play]'), count = bar.querySelector('.anim-count'); function show(i) { index = (i + anim.frames.length) % anim.frames.length; img.src = anim.frames[index]; count.textContent = (index + 1) + ' / ' + anim.frames.length; } function schedule() { clearTimeout(timer); if (playing && visible) timer = setTimeout(function () { show(index + 1); schedule(); }, anim.durations[index]); } function setPlaying(value) { playing = value; playButton.textContent = value ? '暂停' : '播放'; playButton.setAttribute('aria-label', playButton.textContent); schedule(); } bar.addEventListener('click', function (event) { var button = event.target.closest('button'); if (!button) return; if (button.hasAttribute('data-play')) { setPlaying(!playing); return; } setPlaying(false); show(index + Number(button.dataset.step)); }); img.after(bar); show(0); setPlaying(!window.matchMedia('(prefers-reduced-motion: reduce)').matches); if ('IntersectionObserver' in window) { new IntersectionObserver(function (entries) { visible = entries[entries.length - 1].isIntersecting; schedule(); }, { threshold: 0.25 }).observe(img); } else { visible = true; schedule(); } }); } function articleToc(vm) { // 侧边栏中显示当前文章的二、三级标题:文章在侧边栏中时挂在它下面,否则放在侧边栏顶部 var nav = document.querySelector('.sidebar-nav'); var section = document.querySelector('.markdown-section'); if (stopSpy) { stopSpy(); stopSpy = null; } if (!nav || !section) return; nav.querySelectorAll('.page-toc, .page-toc-block').forEach(function (node) { node.remove(); }); // Docsify 按前缀匹配当前项:“首页”的 #/ 会匹配所有文章,地址带 ?id= 时又匹配不到,这里按完整路径重新标记 var target = decode('#' + vm.route.path), active = null; nav.querySelectorAll('li > a').forEach(function (link) { var same = !active && decode((link.getAttribute('href') || '').split('?')[0]) === target; link.parentNode.classList.toggle('active', same); if (same) active = link.parentNode; }); if (vm.route.path === '/') return; drawHot(section); var heads = Array.from(section.querySelectorAll('h2[id], h3[id]')); if (heads.length < 2) return; var route = '#' + encodedRoute(vm.route.path) + '?id='; var root = document.createElement('ul'); root.className = 'page-toc'; var group = null, flat = false, links = {}; heads.forEach(function (heading) { var title = heading.textContent.trim(); if (heading.tagName === 'H3' && (flat || !group)) return; var li = document.createElement('li'); var a = document.createElement('a'); a.href = route + encodeURIComponent(heading.id); a.textContent = a.title = title; li.appendChild(a); var mark = heading.querySelector('.hot'); if (mark) a.appendChild(hotBadge(hotLevel(mark))); links[heading.id] = li; if (heading.tagName === 'H2') { root.appendChild(li); group = li; flat = /^(附录|参考资料)/.test(title); } else { var sub = group.querySelector(':scope > ul'); if (!sub) { sub = document.createElement('ul'); group.appendChild(sub); group.classList.add('has-sub'); } sub.appendChild(li); } }); if (active) { active.appendChild(root); } else { var block = document.createElement('div'); block.className = 'page-toc-block'; var name = document.createElement('p'); name.className = 'page-toc-title'; name.textContent = (section.querySelector('h1') || {}).textContent || decode(vm.route.path.split('/').pop()); block.appendChild(name); block.appendChild(root); nav.insertBefore(block, nav.firstChild); } var sidebar = document.querySelector('.sidebar'); var pending = false; function update() { pending = false; var current = null; for (var i = 0; i < heads.length; i++) { if (heads[i].getBoundingClientRect().top > 90) break; if (links[heads[i].id]) current = links[heads[i].id]; } root.querySelectorAll('li.current, li.open').forEach(function (li) { li.classList.remove('current', 'open'); }); if (!current) return; current.classList.add('current'); var top = current.parentNode === root ? current : current.parentNode.parentNode; top.classList.add('open'); if (sidebar) { var offset = current.getBoundingClientRect().top - sidebar.getBoundingClientRect().top; if (offset < 60 || offset > sidebar.clientHeight - 60) sidebar.scrollTop += offset - sidebar.clientHeight / 3; } } function onScroll() { if (!pending) { pending = true; requestAnimationFrame(update); } } window.addEventListener('scroll', onScroll, { passive: true }); stopSpy = function () { window.removeEventListener('scroll', onScroll); }; update(); } preserveLegacyLinks(); window.addEventListener('hashchange', preserveLegacyLinks); // 学习路线的文章开头提示可以在学习站中按这条路线定级、排计划 var LEARN_ROUTES = { 'Java 后端': 'java', '大模型应用开发': 'llm', '面试冲刺': 'sprint' }; function learnCallout(vm, section) { var match = decode(vm.route.path).match(/^\/notes\/学习路线 - (.+)$/), id = match && LEARN_ROUTES[match[1]]; if (!section || !id || section.querySelector('.learn-callout')) return; var box = document.createElement('a'); box.className = 'learn-callout'; box.href = '/learn/#/start/' + id; box.innerHTML = '在学习站中按这条路线学先做一轮自评定级,跳过已经掌握的天,按每天的时间和面试日期排出计划,自评过的题按间隔复习。'; var heading = section.querySelector('h1'); (heading || section.firstChild).insertAdjacentElement(heading ? 'afterend' : 'beforebegin', box); } // 学习站中有这篇笔记的题时,文章末尾提示读完练一练(只抽这篇笔记的题,还没开始学也可以练) function practiceCallout(vm, section) { var match = decode(vm.route.path).match(/^\/notes\/(.+)$/), note = match && match[1]; var count = note && (window.CS_NOTES_LEARN_TOPICS || {})[note]; if (!section || !count || count < 2 || section.querySelector('.learn-practice')) return; var box = document.createElement('a'); box.className = 'learn-callout learn-practice'; box.href = '/learn/#/mock/' + encodeURIComponent(note); box.innerHTML = '读完练一练学习站中有 ' + count + ' 道和这篇笔记有关的面试题:先自己答,再对照参考要点自评,自评过的题会按间隔提醒复习。'; // 放在上一篇、下一篇之前 var after = section.querySelector('.page-nav') || section.querySelector('.site-footer'); if (after) after.insertAdjacentElement('beforebegin', box); else section.appendChild(box); } window.$docsify = { name: 'CS-Notes', repo: 'https://github.com/CyC2018/CS-Notes', homepage: 'README.md', loadSidebar: true, alias: { '/.*/_sidebar.md': '/_sidebar.md' }, subMaxLevel: 0, auto2top: true, notFoundPage: true, relativePath: false, copyCode: { buttonText: '复制', errorText: '复制失败', successText: '已复制' }, plugins: [function (hook, vm) { hook.afterEach(function (content) { var document = new DOMParser().parseFromString(content, 'text/html'); configureImages(document, vm.route.path === '/'); var heading = document.querySelector('h1'); var list = heading && heading.nextElementSibling; if (list && list.tagName === 'UL' && list.querySelectorAll('a').length > 5 && Array.from(list.querySelectorAll('a')).every(function (link) { return link.getAttribute('href').includes('?id='); })) { var details = document.createElement('details'); details.className = 'article-toc'; details.innerHTML = '本文目录'; list.replaceWith(details); details.appendChild(list); } return document.body.innerHTML + ''; }); hook.doneEach(function () { var heading = document.querySelector('.markdown-section h1, .markdown-section h2'); document.title = (vm.route.path === '/' || !heading ? 'CS-Notes' : heading.textContent.trim() + ' · CS-Notes') + ' · CyC2018'; configureImages(document.querySelector('.markdown-section'), vm.route.path === '/'); stepAnimations(document.querySelector('.markdown-section')); articleToc(vm); learnCallout(vm, document.querySelector('.markdown-section')); practiceCallout(vm, document.querySelector('.markdown-section')); document.querySelectorAll('.article-toc').forEach(function (toc) { toc.open = window.matchMedia('(min-width: 769px)').matches; }); }); }] }; })();