diff --git a/website/templates/site.js b/website/templates/site.js
index 090e90cc..d639c7fb 100644
--- a/website/templates/site.js
+++ b/website/templates/site.js
@@ -28,6 +28,169 @@
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);
window.$docsify = {
@@ -61,6 +224,8 @@
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);
document.querySelectorAll('.article-toc').forEach(function (toc) {
toc.open = window.matchMedia('(min-width: 769px)').matches;
});