From a4d7b6cf2372ab446d03f7789e10dd948778b3af Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 04:40:57 +0800 Subject: [PATCH 1/4] 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 | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/website/builder/build.py b/website/builder/build.py index 76895fc1..dca55fb0 100644 --- a/website/builder/build.py +++ b/website/builder/build.py @@ -245,12 +245,19 @@ def build(optimize=True): if learn_report['pending']: print(prefix + '学习站:内容与 learn/content.lock.json 不一致,请在本机运行 python3 learn/maintain.py 查看并写入:' + json.dumps(learn_report['pending'], ensure_ascii=False), file=sys.stderr) - learn_topics = {} + learn_topics, learn_sections = {}, {} 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.setdefault(note, []).append(question['id']) + # 小节标题下的“面试题”:笔记 → 小节锚点 → [题号, 题目, 热度] + for link in question['links']: + if link.get('note') and '?id=' in link['href']: + anchor = urllib.parse.unquote(link['href'].split('?id=', 1)[1]) + entries = learn_sections.setdefault(link['note'], {}).setdefault(anchor, []) + if all(entry[0] != question['id'] for entry in entries): + entries.append([question['id'], question['text'], question['heat']]) 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'))) @@ -261,7 +268,8 @@ def build(optimize=True): 'window.CS_NOTES_LEGACY_ROUTES = ' + json.dumps(legacy, ensure_ascii=False, separators=(',', ':')) + ';\n' 'window.CS_NOTES_SEARCH_INDEX = "/' + index_name + '";\n' 'window.CS_NOTES_ANIMATIONS = ' + json.dumps(animations, ensure_ascii=False, separators=(',', ':')) + ';\n' - 'window.CS_NOTES_LEARN_TOPICS = ' + json.dumps(learn_topics, ensure_ascii=False, separators=(',', ':')) + ';\n') + 'window.CS_NOTES_LEARN_TOPICS = ' + json.dumps(learn_topics, ensure_ascii=False, separators=(',', ':')) + ';\n' + 'window.CS_NOTES_LEARN_SECTIONS = ' + json.dumps(learn_sections, ensure_ascii=False, separators=(',', ':')) + ';\n') generator = hashlib.sha256() learn = [*LEARN_BUILD.parent.glob('*.py'), *(LEARN_BUILD.parent / 'src').rglob('*')] if LEARN_BUILD.is_file() else [] for resource in sorted([*Path(__file__).parent.glob('*.py'), *SITE.rglob('*'), *learn]): From 6f16fa7e9501f4b50c46a2ecc6be7a857e4e4ace Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 04:40:57 +0800 Subject: [PATCH 2/4] Update site.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 有面试题的小节标题下列出这一节的题,点进去只练这一节,练过的显示已练或到期。 --- website/templates/site.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/website/templates/site.js b/website/templates/site.js index a91e0cb4..1f884f22 100644 --- a/website/templates/site.js +++ b/website/templates/site.js @@ -225,6 +225,31 @@ banner.querySelector('.learn-banner-text span').textContent = (due ? '今天有 ' + due + ' 道题到了该复习的时候,' : '') + '已经学完 ' + done + ' 天' + (due ? '' : ',接着学下一天'); banner.querySelector('.learn-banner-go').textContent = '继续'; } + // 小节标题下列出这一节的面试题(显示第一道的题目和题数),点进去只练这一节;练过的显示已练或到期 + 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 = 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]; @@ -277,6 +302,7 @@ 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')); document.querySelectorAll('.article-toc').forEach(function (toc) { toc.open = window.matchMedia('(min-width: 769px)').matches; From bdde020b696e087caf7519510caa5eb07432121f Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 04:40:58 +0800 Subject: [PATCH 3/4] Update site.css MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充小节面试题入口的样式。 --- website/templates/site.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/templates/site.css b/website/templates/site.css index 5561f228..a9ffe476 100644 --- a/website/templates/site.css +++ b/website/templates/site.css @@ -48,6 +48,12 @@ body { color: #34495e; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI .learn-banner-go { flex: none; padding: 7px 18px; border-radius: 8px; background: var(--theme-color); color: #fff; font-weight: 600; font-size: 15px; } .learn-callout { display: block; } .learn-practice { margin-top: 36px; } +.learn-q { display: flex; align-items: center; gap: 8px; margin: -6px 0 16px; padding: 7px 12px; border: 1px solid #e3e6e4; border-radius: 8px; background: #fafbfa; color: inherit; font-size: 14px; line-height: 1.5; text-decoration: none !important; } +.learn-q:hover, .learn-q:focus-visible { border-color: var(--theme-color); background: #eef7f2; } +.learn-q-label { flex: none; padding: 1px 7px; border-radius: 5px; background: #e3f2ea; color: var(--theme-color); font-size: 12px; font-weight: 600; } +.learn-q-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #34495e; } +.learn-q-go { flex: none; color: var(--theme-color); font-weight: 600; } +.learn-q.due .learn-q-go { color: #c2410c; } @media (min-width: 769px) { .content { padding-top: 12px; } .article-toc { display: none; } } .page-toc-block { margin: 0 0 18px; padding: 0 0 14px 18px; border-bottom: 1px solid #e2e3dc; } .page-toc-title { margin: 0 0 6px; font-size: 14px; font-weight: 600; color: #34495e; } From eefcfdce5554378e8daca52f78363947066ae1cd Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 04:40:58 +0800 Subject: [PATCH 4/4] 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 c59ce03c..d327da3b 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 记录每篇笔记的题号);已经在学的读者,入口和首页横幅读取同一网站下的学习记录(localStorage 的 cs-learn-v1,不上传),写出练过几道、今天有几道到期;不存在时只构建笔记,没有入口。 +学习站(定级、每日计划、间隔复习)的代码不在本仓库,网站工作流从私有仓库检出到 learn/;learn/ 存在时,build.py 调用 learn/build.py 生成到 dist/learn/,随网站一起发布,并在首页、侧边栏和三条学习路线的文章开头加入口,学习站中有题的笔记在文章末尾提示“读完练一练”(search-paths.js 中的 CS_NOTES_LEARN_TOPICS 记录每篇笔记的题号);已经在学的读者,入口和首页横幅读取同一网站下的学习记录(localStorage 的 cs-learn-v1,不上传),写出练过几道、今天有几道到期;有题的小节标题下列出这一节的面试题(CS_NOTES_LEARN_SECTIONS:笔记 → 锚点 → 题号、题目、热度),点进去只练这一节;不存在时只构建笔记,没有入口。 ## 自动更新与发布