Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ on:
- 'assets/**'
- 'docs/_media/**'
- 'website/**'
- 'learn/**'
schedule:
# UTC; Beijing time 09:17 and 21:17.
- cron: '17 1,13 * * *'
Expand Down Expand Up @@ -59,7 +60,13 @@ jobs:
python website/builder/test_site_assets.py
python website/deploy/test_package_site.py
node website/builder/test_search.cjs
node learn/tests/core.test.cjs website/dist/learn/data.json
python learn/tests/test_content.py
npm test --prefix website/deploy
- name: 检查学习站的内容对照
# 只提醒不阻塞:路线改了但没有运行 learn/maintain.py --write 时,读者的记录仍然保留,只是更新记录没有日期
continue-on-error: true
run: python learn/maintain.py
- name: 发布网页和图片并校验正式域名
if: github.repository == 'CyC2018/CS-Notes' && github.ref == 'refs/heads/master' && github.event_name != 'pull_request'
env:
Expand Down
2 changes: 2 additions & 0 deletions learn/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
__pycache__/
93 changes: 93 additions & 0 deletions learn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# CS-Notes 学习站

从三条学习路线(Java 后端、大模型应用开发、面试冲刺)生成的静态站点,与笔记网站一起部署在 `/learn/`。读者先用路线中的自测题自评定级,按水平、每天的时间和面试日期排出计划,自评过的题按 1、3、7、15、30、60 天的间隔复习。不需要账号,不连接后端或 AI 接口,记录只保存在浏览器的 localStorage 中,可以导出和导入。

## 功能

- 今天:只给一个“下一步”(继续定级、先复习一批、学今天的内容),下面是连续学习天数、最近 7 天和预计学完的日期;中断几天后回来时提示,并把到期的题分批复习;
- 复习份额:每天的复习按设定的时间分批,做完份额先学当天的内容;积压超过两批时份额加倍;时间紧时可以“只有 5 分钟”,复习 5 道保住连续天数;
- 连续学习:每学满 7 天得到 1 次保护(最多 2 次),中断一天自动使用,不清零;连续天数到 3、7、14、30 等数字时在完成页提示;
- 定级:每个阶段两三道题,看参考要点后自评,“模糊”算半个“会”;
- 计划:按路线顺序装进每天的时间,先留出复习的时间;今天完成的天仍算在今天;可以导出 .ics 加到日历;
- 每天:导读、原文链接、自测题卡、动手和算法;完成后显示进度和下一步,学完一个阶段单独提示;
- 复习与模拟面试:键盘 空格、1、2、3 操作;模拟面试按热度和不牢的程度抽 8 道题,结束时给出得分和需要再看的题;
- 题库:按路线、热度、掌握情况和关键词找题,每道题有单独的链接(`#/q/<id>`),可以分享;
- 进度:16 周热力图、各阶段掌握、常忘的题,以及在本地用 canvas 生成的 1080×1440 分享图(带指向本站的二维码,不含学习记录);
- 回答对照:写了回答再看要点时,列出要点中的术语哪些提到了、哪些没提到(只作提示,不打分);
- 反馈:题卡上的“要点有误”和页脚的“反馈建议”打开 GitHub 新建议题页,预填题号、题目和要点,读者自己提交;维护者按题号在路线文件中修改;
- 题卡上的“复制给 AI 追问”把题目、回答、参考要点和原文链接整理成提示词;
- 学习记录:导出成文件或复制成文字,在另一台设备上选择文件、拖入或粘贴导入;导入前显示两边的概况,可以合并(同一道题取最近一次自评)或替换;进度页在记录较多又久未导出时轻提示备份;
- 数据与隐私(页脚链接,`#/privacy`):不收集个人信息、不用 Cookie 和第三方脚本、记录只在本机、访问日志由托管服务商按其规则记录、怎样转移和删除;内容有变化时更新页面上的日期;
- 外观:清爽(默认)、纸面、活泼三套配色,在学习记录或设置页切换,只存在本机;
- 手机上导航在底部;可以安装到桌面,载入一次后离线可用;深色模式跟随系统。

## 构建与预览

```bash
python3 learn/build.py --notes-base 'http://127.0.0.1:8769/#/notes/' # 单独构建到 learn/dist,链接指向本机的笔记网站
python3 learn/serve.py # http://127.0.0.1:8770/
```

笔记网站的 `website/builder/build.py` 会调用 `learn/build.py`,把学习站生成到 `website/dist/learn/`,题目链接改为站内的 `/#/notes/…`,并在首页、侧边栏和学习路线的文章中加入口;随网站一起发布,不需要单独部署。单独构建时严格检查所有指向笔记的链接,锚点不存在时报错退出;和网站一起构建时用 --lenient,只给警告(见下文“维护”)。地址后加 `?today=2026-11-02` 可以模拟日期。

图标和链接预览图由 `python3 learn/make_images.py` 生成到 `src/icons/` 和 `src/og.png`(需要 Pillow,先构建一次以读取题数)。

## 维护

学习站的内容全部来自三条学习路线和它们链接到的笔记,笔记更新后随网站一起重新构建,不需要单独发布。读者的记录按题目和天的编号保存,编号由 content.py 根据提交在仓库中的 content.lock.json 分配,保证笔记更新后不变:

- 改了题目的问法或参考要点:按文字和要点的相似度找回原编号,复习记录不受影响;
- 新增题、新增天:拿新编号;新天的编号接着这条路线用过的最大序号,插在中间也不会让后面的天换编号;
- 学过的天改了导读或阅读范围(lock 中记录每天的指纹):完成状态不变,首页列出这些天,读者可以再看一眼;
- 删掉的题和天:标记为下线,读者的记录移到归档中保留,不再安排复习;重新加回来时恢复原编号和记录;
- 路线下线(从 routes.json 删掉):在学这条路线的读者看到换路线的提示,路线中相同的题接着按原来的间隔复习;新增路线在 routes.json 中加一项。

改了学习路线或笔记标题后,在本机运行:

```bash
python3 learn/maintain.py # 列出新增、修改、下线的题和天,以及链接到的小节不存在等问题
python3 learn/maintain.py --write # 确认无误后写入 lock,这次变化记入更新记录,读者在首页看到和自己有关的部分
```

自动判断错了时(两道不同的题被当成同一道,或改写后没认出来),用 `--separate 新题临时编号` 或 `--map 新题临时编号=旧编号` 指定,临时编号在 maintain.py 的输出中。没有运行 maintain.py 就提交也能正常发布:CI 中宽松构建,读者的记录照样保留,只是更新记录没有日期;构建日志和 dist-report.json 的 learn 一项会提示 lock 不一致,链接到的小节改名时改为链接到文章开头并给出警告,不挡住笔记的发布。

首页的“内容更新”只列和读者自己有关的部分:学过的天新增的题(可以一键练习)、当前路线新增的天、练过的题改了什么(带链接)、学过的天改了导读、练过的题下线。页面只带最近 20 条更新,落下更多的读者只看到一句概括。还没发布过时不需要更新记录:第一次发布前运行 `python3 learn/maintain.py --baseline`,写入现在的内容并清空更新记录。

页面一侧:本地记录有版本号(现在是 2),旧版本的记录读入时自动转换;另一个标签页已经用更新版本保存过时,旧页面只读并提示刷新。部署新版本后,打开着的页面在切回前台时检查更新,提示刷新;离线缓存按构建号整体替换。

## 上线步骤

1. 第一次发布前运行 `python3 learn/maintain.py --baseline`,按现在的内容写入 lock 并清空更新记录;之后每次改路线用 `maintain.py` 和 `--write`;
2. 提交 learn/(含 content.lock.json、routes.json、src/icons、src/og.png)、website/ 中学习站相关的改动和 .github/workflows/website.yml;
3. CI 构建网站时生成 /learn/,跑核心逻辑、编号和网站的测试,发布后校验 /learn/ 的入口、数据和离线缓存脚本;
4. 上线后在手机和电脑上各打开一次 https://cyc2018.xyz/learn/:能定级、能学一天、刷新后记录还在、断网后能打开。

读者反馈的题目问题,用 `python3 learn/maintain.py --find 题号` 找到所在的天和路线文件。

## 上线与安全

- 入口页带内容安全策略(CSP):只允许本站的脚本、样式、图片和请求,head 中的内联脚本由 build.py 计算哈希写入;以后加内联脚本无需改动,但不能加 onclick 这类内联事件。e2e 测试把 CSP 拦截记为错误;
- 页面渲染出错时显示“页面出错了”,提供刷新和直接导出记录,不会白屏;题目数据载入失败时可以重试;
- 发布后的线上校验(website/deploy/deploy.mjs)同时核对 learn/index.html、data.json、app.js、sw.js,以及目录地址 /learn/ 返回的是学习站入口页;
- 旧浏览器(不支持 color-mix 等)有颜色回退,顶部和底部导航不会透明。

## 测试

```bash
node learn/tests/core.test.cjs # 调度、定级、计划、日历、导入校验;dist 存在时附带真实数据的检查
python3 learn/tests/e2e.py --notes http://127.0.0.1:8769/ # 无头 Edge 走完整流程,需要 websocket-client
python3 learn/tests/personas.py # 访客、连续 20 天学习、中断后回来、模拟面试、离线等场景,约 10 分钟
python3 learn/tests/test_content.py # 编号对应:改问法、改要点、插入一天、删了再加回、路线增删、手工指定
python3 learn/tests/maintenance.py # 读者学了 3 天后笔记陆续更新、部署:记录保留、更新提示、刷新提示、路线下线,约 3 分钟
python3 learn/tests/replay_history.py # 按 Git 历史逐个提交重放路线的修改,检查编号是否稳定
```

e2e.py 和 personas.py 默认测试 8770 上的单独构建,加 `--base http://127.0.0.1:8769/learn/` 测试笔记网站中的版本。

## 文件

- build.py:解析 routes.json 中的路线,生成 dist/data.json,复制 src,给静态文件加版本号,写入离线缓存的文件列表;
- content.py、content.lock.json:题目和天的稳定编号与更新记录;maintain.py:查看变化并写入 lock;routes.json:放进学习站的路线;
- src/core.js:不依赖页面的逻辑(日期、调度、定级、计划、模拟面试抽题、日历、提示词、导入校验),浏览器和 Node 共用;
- src/qr.js:二维码生成(字节模式、纠错等级 M、版本 1~10);
- src/app.js、app.css、index.html:页面;sw.js、manifest.webmanifest:离线缓存和安装。
Loading