From aa946befcec5ca4412dd58fc05779a6bde96a911 Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 00:45:31 +0800 Subject: [PATCH 1/3] Update build.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit search-paths.js 中记录每篇笔记在学习站中的题号。 --- website/builder/build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/builder/build.py b/website/builder/build.py index 10920159a..76895fc18 100644 --- a/website/builder/build.py +++ b/website/builder/build.py @@ -247,10 +247,10 @@ def build(optimize=True): + json.dumps(learn_report['pending'], ensure_ascii=False), file=sys.stderr) learn_topics = {} if learn_report is not None: - # 每篇笔记在学习站中有几道题(题目的原文链接指向这篇笔记),文章末尾据此提示“读完练一练” + # 每篇笔记在学习站中的题(题目的原文链接指向这篇笔记),文章末尾据此提示“读完练一练”,并按读者的记录写出练过几道 for question in json.loads((dist / 'learn' / 'data.json').read_text())['questions'].values(): for note in {link['note'] for link in question['links'] if link.get('note')}: - learn_topics[note] = learn_topics.get(note, 0) + 1 + learn_topics.setdefault(note, []).append(question['id']) copy_referenced_assets(dist, ROOT) image_report = optimize_images(dist, CACHE) if optimize else {'converted': 0} index_name, search_report = write_search_index(dist, sorted((dist / 'notes').glob('*.md'))) From 4ad13b2aabe2580d7070ece88d1c49dc28fd5f05 Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 00:45:31 +0800 Subject: [PATCH 2/3] Update site.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 学习站入口按读者在本机的学习记录显示进度:笔记末尾写出练过几道、几道到期,首页横幅改为继续学习。 --- website/templates/site.js | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/website/templates/site.js b/website/templates/site.js index 8da3a5866..a91e0cb4e 100644 --- a/website/templates/site.js +++ b/website/templates/site.js @@ -206,14 +206,38 @@ (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 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 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 = '读完练一练学习站中有 ' + count + ' 道和这篇笔记有关的面试题:先自己答,再对照参考要点自评,自评过的题会按间隔提醒复习。'; + 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); @@ -253,6 +277,7 @@ articleToc(vm); learnCallout(vm, document.querySelector('.markdown-section')); practiceCallout(vm, document.querySelector('.markdown-section')); + learnBanner(document.querySelector('.markdown-section')); document.querySelectorAll('.article-toc').forEach(function (toc) { toc.open = window.matchMedia('(min-width: 769px)').matches; }); From 99749872392fcc77a38fe535b939699705c70b1e Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 00:45:31 +0800 Subject: [PATCH 3/3] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充学习站入口显示进度的说明。 --- website/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/README.md b/website/README.md index de89d4afe..c59ce03cc 100644 --- a/website/README.md +++ b/website/README.md @@ -43,7 +43,7 @@ verify_updates.py 在临时副本中修改文章、替换配图、新增文章 页面模板保留手机分类导航、折叠目录、搜索和旧文章入口兼容;固定版本的浏览器依赖及许可证保存在 templates/vendor 中。 -学习站(定级、每日计划、间隔复习)的代码不在本仓库,网站工作流从私有仓库检出到 learn/;learn/ 存在时,build.py 调用 learn/build.py 生成到 dist/learn/,随网站一起发布,并在首页、侧边栏和三条学习路线的文章开头加入口,学习站中有题的笔记在文章末尾提示“读完练一练”(search-paths.js 中的 CS_NOTES_LEARN_TOPICS 记录每篇笔记的题数);不存在时只构建笔记,没有入口。 +学习站(定级、每日计划、间隔复习)的代码不在本仓库,网站工作流从私有仓库检出到 learn/;learn/ 存在时,build.py 调用 learn/build.py 生成到 dist/learn/,随网站一起发布,并在首页、侧边栏和三条学习路线的文章开头加入口,学习站中有题的笔记在文章末尾提示“读完练一练”(search-paths.js 中的 CS_NOTES_LEARN_TOPICS 记录每篇笔记的题号);已经在学的读者,入口和首页横幅读取同一网站下的学习记录(localStorage 的 cs-learn-v1,不上传),写出练过几道、今天有几道到期;不存在时只构建笔记,没有入口。 ## 自动更新与发布