(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; var first = root.querySelector('img'); 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 = !homepage && img === first ? 'eager' : 'lazy'; } img.decoding = 'async'; // 笔记中的配图大多没写说明,读屏软件只能读出文件名;用所在小节的标题作为说明 if (!img.hasAttribute('alt')) { var heading = null; for (var node = img; node && node !== root && !heading; node = node.previousElementSibling || node.parentElement) { if (/^H[1-6]$/.test(node.tagName) && node !== img) heading = node; } var title = heading ? heading.textContent.replace(/[#¶]/g, '').trim() : ''; img.alt = title ? title + (/[!-~]$/.test(title) ? ' ' : '') + '的配图' : '配图'; } }); // 复制代码插件写的 aria-hidden="hidden" 不是有效值;插件的按钮在本插件之后才加上 setTimeout(function () { root.querySelectorAll('.docsify-copy-code-button [aria-hidden="hidden"]').forEach(function (node) { node.setAttribute('aria-hidden', 'true'); }); }); } // 离线时提示显示的是缓存的版本;sw.js 缓存读过的文章,离线时也能打开 function offlineNote(section) { if (!section || section.querySelector('.offline-note')) return; // sw.js 给没缓存过的文章返回的说明页:Docsify 会把它记在内存里,联网后要刷新页面才能重新下载 if (/^当前离线/.test((section.querySelector('h1') || {}).textContent || '')) { var retry = document.createElement('p'); retry.innerHTML = '重新加载'; section.appendChild(retry); window.addEventListener('online', function () { location.reload(); }, { once: true }); return; } if (navigator.onLine !== false) return; var note = document.createElement('p'); note.className = 'offline-note'; note.setAttribute('role', 'status'); note.textContent = '当前离线,显示的是这台设备上次缓存的内容。'; section.insertBefore(note, section.firstChild); } if ('serviceWorker' in navigator && (location.protocol === 'https:' || location.hostname === '127.0.0.1' || location.hostname === 'localhost')) { window.addEventListener('load', function () { navigator.serviceWorker.register('/sw.js').catch(function () {}); }); } // Docsify 生成的目录按钮和 GitHub 角标是英文名称,读屏软件读成 Menu、View source on GitHub function nameControls() { var toggle = document.querySelector('.sidebar-toggle'); if (toggle) { toggle.setAttribute('aria-label', '打开或收起目录'); toggle.title = '目录'; } var corner = document.querySelector('.github-corner'); if (corner) corner.setAttribute('aria-label', '在 GitHub 上查看源码'); } function skipLink() { if (document.querySelector('.skip-link')) return; var skip = document.createElement('button'); skip.type = 'button'; skip.className = 'skip-link'; skip.textContent = '跳到正文'; skip.addEventListener('click', function () { var section = document.querySelector('.markdown-section'); if (!section) return; section.setAttribute('tabindex', '-1'); section.focus(); }); document.body.insertBefore(skip, document.body.firstChild); } 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); } // 学习站中有这篇笔记的题时,文章末尾提示读完练一练(只抽这篇笔记的题,还没开始学也可以练) // 学习站的记录:同一个网站,保存在这个浏览器的 localStorage 中,不上传;入口处据此写出读者自己的进度 function learnRecord() { try { var record = JSON.parse(localStorage.getItem('cs-learn-v1')); return record && record.cards ? record : null; } catch (error) { return null; } } function localDay() { var d = new Date(); return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2); } // 首页的学习站横幅:已经在学的读者显示今天到期的复习和完成的天数,按钮改为“继续” function learnBanner(section) { var banner = section && section.querySelector('.learn-banner'), record = learnRecord(); if (!banner || !record || !record.profile || banner.dataset.personal) return; var today = localDay(), cards = record.cards, done = Object.keys(record.done || {}).length; var due = Object.keys(cards).filter(function (id) { return cards[id].due && cards[id].due <= today; }).length; banner.dataset.personal = '1'; banner.querySelector('b').textContent = '继续学习'; banner.querySelector('.learn-banner-text span').textContent = (due ? '今天有 ' + due + ' 道题到了该复习的时候,' : '') + '已经学完 ' + done + ' 天' + (due ? '' : ',接着学下一天'); banner.querySelector('.learn-banner-go').textContent = '继续'; } // 首页的“今日面试题”:每天按日期换一道面试最常问的题(同一天所有读者看到的一样),链接到这道题的页面,先想再看要点 function dailyQuestion(section) { var banner = section && section.querySelector('.learn-banner'), sections = window.CS_NOTES_LEARN_SECTIONS; if (!banner || !sections || section.querySelector('.learn-daily')) return; var seen = {}, hot = []; Object.keys(sections).forEach(function (note) { Object.keys(sections[note]).forEach(function (anchor) { sections[note][anchor].forEach(function (q) { if (q[2] > 1 && !seen[q[0]]) { seen[q[0]] = true; hot.push(q); } }); }); }); if (!hot.length) return; hot.sort(function (a, b) { return a[0] < b[0] ? -1 : 1; }); var p = localDay().split('-').map(Number), q = hot[Math.floor(Date.UTC(p[0], p[1] - 1, p[2]) / 864e5) % hot.length]; var record = learnRecord(), done = record && record.cards[q[0]] && record.cards[q[0]].last === localDay(); var link = document.createElement('a'), label = document.createElement('span'), text = document.createElement('span'), go = document.createElement('span'); link.className = 'learn-q learn-daily'; link.href = '/learn/q/' + q[0] + '.html'; label.className = 'learn-q-label'; label.textContent = '今日面试题'; text.className = 'learn-q-text'; text.textContent = q[1]; go.className = 'learn-q-go'; go.textContent = done ? '今天练过了' : '想一想'; link.appendChild(label); link.appendChild(text); link.appendChild(go); banner.insertAdjacentElement('afterend', link); } // 小节标题下列出这一节的面试题(显示第一道的题目和题数),点进去只练这一节;练过的显示已练或到期 function sectionQuestions(vm, section) { var match = decode(vm.route.path).match(/^\/notes\/(.+)$/), note = match && match[1]; var sections = note && (window.CS_NOTES_LEARN_SECTIONS || {})[note]; if (!section || !sections || section.querySelector('.learn-q')) return; var record = learnRecord(), today = localDay(); Object.keys(sections).forEach(function (anchor) { var heading = (section.ownerDocument || document).getElementById(anchor), list = sections[anchor]; if (!heading || !section.contains(heading) || !/^H[2-6]$/.test(heading.tagName)) return; var tried = record ? list.filter(function (q) { return record.cards[q[0]]; }) : []; var due = tried.filter(function (q) { var c = record.cards[q[0]]; return c.due && c.due <= today; }).length; var link = document.createElement('a'); link.className = 'learn-q' + (due ? ' due' : ''); link.href = '/learn/#/mock/' + encodeURIComponent(note + '@' + anchor); var label = document.createElement('span'), text = document.createElement('span'), go = document.createElement('span'); label.className = 'learn-q-label'; label.textContent = list.some(function (q) { return q[2] > 1; }) ? '面试最常问' : '面试题'; text.className = 'learn-q-text'; text.textContent = list[0][1]; go.className = 'learn-q-go'; go.textContent = due ? '到期复习' : tried.length === list.length ? '已练 · 再练' : list.length > 1 ? '练 ' + list.length + ' 道' : '练一练'; link.appendChild(label); link.appendChild(text); link.appendChild(go); heading.insertAdjacentElement('afterend', link); }); } function practiceCallout(vm, section) { var match = decode(vm.route.path).match(/^\/notes\/(.+)$/), note = match && match[1]; var ids = note && (window.CS_NOTES_LEARN_TOPICS || {})[note]; if (!section || !ids || ids.length < 2 || section.querySelector('.learn-practice')) return; var record = learnRecord(), today = localDay(); var tried = record ? ids.filter(function (id) { return record.cards[id]; }) : []; var due = tried.filter(function (id) { return record.cards[id].due && record.cards[id].due <= today; }); var box = document.createElement('a'); box.className = 'learn-callout learn-practice'; box.href = '/learn/#/mock/' + encodeURIComponent(note); box.innerHTML = '读完练一练' + (tried.length ? '这篇笔记有 ' + ids.length + ' 道面试题,你练过 ' + tried.length + ' 道' + (due.length ? ',其中 ' + due.length + ' 道到了该复习的时候' : '') + '。再练一组,自评后按间隔安排复习。' : '学习站中有 ' + ids.length + ' 道和这篇笔记有关的面试题:先自己答,再对照参考要点自评,自评过的题会按间隔提醒复习。') + ''; // 放在上一篇、下一篇之前 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.txt', ext: '.txt', loadSidebar: true, alias: { '/.*/_sidebar.txt': '/_sidebar.txt' }, 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); } sectionQuestions(vm, document.body); return document.body.innerHTML + ''; }); hook.doneEach(function () { nameControls(); skipLink(); offlineNote(document.querySelector('.markdown-section')); 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')); sectionQuestions(vm, document.querySelector('.markdown-section')); learnBanner(document.querySelector('.markdown-section')); dailyQuestion(document.querySelector('.markdown-section')); document.querySelectorAll('.article-toc').forEach(function (toc) { toc.open = window.matchMedia('(min-width: 769px)').matches; }); }); }] }; })();