From f0ad247776421f0c7ce88acc3540fa5c06d0e821 Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 20:36:00 +0800 Subject: [PATCH 1/2] Update site.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 读完的文章在侧边栏标 ✓(读到附录或参考资料,且读过的小节不少于一半),首页写已读完几篇并可清除阅读记录;读完的文章再次打开不提示继续阅读。 --- website/templates/site.js | 47 ++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/website/templates/site.js b/website/templates/site.js index a4ab4904..24dd555e 100644 --- a/website/templates/site.js +++ b/website/templates/site.js @@ -760,6 +760,17 @@ }); } + // 侧边栏中读完的文章后面标 ✓ + function markRead() { + var all = storage(PROGRESS_KEY) || {}; + document.querySelectorAll('.sidebar-nav a[href^="#/"]').forEach(function (link) { + var href = link.getAttribute('href'); + if (href.indexOf('?') !== -1) return; + var done = !!(all[decode(href.slice(1))] || {}).done; + link.classList.toggle('read', done); + if (done) link.setAttribute('aria-description', '已读完'); else link.removeAttribute('aria-description'); + }); + } // 记住每篇文章读到的小节:再次打开时提示继续阅读,首页给出最近读过的文章 var stopProgress = null; function readingProgress(vm, section) { @@ -780,6 +791,16 @@ link.href = '#' + encodedRoute(last) + (saved[last].id ? '?id=' + encodeURIComponent(saved[last].id) : ''); link.textContent = saved[last].page + (saved[last].heading ? ' · ' + saved[last].heading : ''); recent.appendChild(link); + var finished = Object.keys(saved).filter(function (key) { return saved[key].done; }).length; + if (finished) { + recent.insertAdjacentHTML('beforeend', '已读完 ' + finished + ' 篇'); + recent.querySelector('.clear-read').addEventListener('click', function () { + if (!window.confirm('清除全部阅读记录?侧边栏的已读标记、上次阅读的位置都会清除。')) return; + try { localStorage.removeItem(PROGRESS_KEY); } catch (error) {} + recent.remove(); + markRead(); + }); + } grid.after(recent); return; } @@ -788,7 +809,9 @@ var page = ((section.querySelector('h1') || {}).textContent || path.split('/').pop()).trim(); var record = saved[path]; var target = record && record.id && document.getElementById(record.id); - if (!vm.route.query.id && target && heads.indexOf(target) > 1) { + var tail = heads.filter(function (h) { return h.tagName === 'H2' && /^(附录|参考资料)/.test(headingText(h)); })[0]; + var finishedHere = record && record.done && tail && target && (target === tail || tail.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_FOLLOWING); + if (!vm.route.query.id && target && heads.indexOf(target) > 1 && !finishedHere) { var toast = document.createElement('div'); toast.className = 'resume-toast'; toast.setAttribute('role', 'status'); @@ -811,21 +834,32 @@ setTimeout(function () { toast.remove(); }, 12000); } var lastId = null, timer = null; + // 读完:读到附录或参考资料(或文末),并且读过的二级小节不少于一半;只跳到文末不算 + var parts = heads.filter(function (h) { return h.tagName === 'H2'; }); + var body = parts.filter(function (h) { return !tail || h.compareDocumentPosition(tail) & Node.DOCUMENT_POSITION_FOLLOWING; }); + var seen = (record && record.seen || []).slice(), done = !!(record && record.done); function save() { - var current = null; + var current = null, part = null; for (var i = 0; i < heads.length; i++) { if (heads[i].getBoundingClientRect().top > 90) break; current = heads[i]; + if (current.tagName === 'H2') part = current; } var id = current ? current.id : null; - if (id === lastId) return; + var changed = id !== lastId; + if (part && body.indexOf(part) !== -1 && seen.indexOf(part.id) === -1) { seen.push(part.id); changed = true; } + var end = (tail && tail.getBoundingClientRect().top < window.innerHeight * .6) || + window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 400; + if (!done && end && seen.length >= Math.max(1, Math.ceil(body.length / 2))) { done = true; changed = true; } + if (!changed) return; lastId = id; var all = storage(PROGRESS_KEY) || {}; - all[path] = { page: page, id: id, heading: current ? headingText(current) : '', time: Date.now() }; - // 只保留最近的 100 篇 - Object.keys(all).sort(function (a, b) { return all[b].time - all[a].time; }).slice(100) + all[path] = { page: page, id: id, heading: current ? headingText(current) : '', time: Date.now(), seen: seen, done: done }; + // 只保留最近的 300 篇 + Object.keys(all).sort(function (a, b) { return all[b].time - all[a].time; }).slice(300) .forEach(function (key) { delete all[key]; }); storage(PROGRESS_KEY, all); + if (done) markRead(); } function onScroll() { clearTimeout(timer); timer = setTimeout(save, 400); } window.addEventListener('scroll', onScroll, { passive: true }); @@ -1017,6 +1051,7 @@ learnBanner(section); dailyQuestion(section); if (section) readingProgress(vm, section); + markRead(); document.querySelectorAll('.article-toc').forEach(function (toc) { toc.open = window.matchMedia('(min-width: 769px)').matches; }); From dc19c4b7970b81d275922c39f51764fdd8163764 Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 20:36:00 +0800 Subject: [PATCH 2/2] Update site.css MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 侧边栏读完标记的样式;手机上打开目录时正文加遮罩,底部按钮条用页面底色。 --- website/templates/site.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/website/templates/site.css b/website/templates/site.css index 600243ec..f577aa50 100644 --- a/website/templates/site.css +++ b/website/templates/site.css @@ -334,3 +334,16 @@ html { scrollbar-gutter: stable; } .token.namespace { opacity: 1; color: var(--muted); } .article-toc li a { display: inline-block; padding: 3px 0; } .markdown-section :not(pre) > code { overflow-wrap: anywhere; } + +/* sidebar-scrim:手机上打开目录时,底部按钮条用页面底色,正文盖一层淡的遮罩 */ +@media (max-width: 768px) { + body.close .sidebar-toggle { background-color: var(--bg); box-shadow: 0 -1px 0 var(--border); } + .content::after { content: ""; position: fixed; inset: 0; z-index: 25; background: rgba(20, 24, 23, .32); opacity: 0; pointer-events: none; transition: opacity .25s ease; } + body.close .content::after { opacity: 1; pointer-events: auto; } +} + +/* 读完的文章:侧边栏标 ✓,首页写已读完几篇 */ +.sidebar-nav a.read::after { content: "✓"; content: "✓" / ""; margin-left: 6px; color: var(--theme-color); font-size: 12px; font-weight: 600; } +.markdown-section .recent-read .read-count { margin-left: 12px; } +.markdown-section .recent-read .clear-read { margin-left: 8px; padding: 2px 4px; border: 0; background: none; color: var(--muted); font: inherit; font-size: 13px; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; } +.markdown-section .recent-read .clear-read:hover { color: var(--theme-color); }