diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..2ca5caf
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,32 @@
+name: tests
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ env:
+ PYTHONUTF8: "1"
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.13"
+ - name: Frontend tests
+ run: node --test "tests/*.test.cjs"
+ - name: Python tests
+ run: python -B -m unittest discover -s tests -p "test_*.py"
diff --git a/.gitignore b/.gitignore
index da0a802..9b56424 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ data/.update-backups/
data/.bookmark-backups/
data/.update-stage-*/
data/bookmarks.html
+data/.data-build.json
web/data.js
.claude/
installer/
diff --git a/README.md b/README.md
index 39b9081..f6a2fdf 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ Chrome / Edge / Safari 书签同步 · 多主题与十六种布局 · 动态天
### 分类与常用操作
- 点击“我的书签”旁的“整理分类”,拖动分类把手或使用箭头调整区块顺序;横向排列用左右箭头和 Alt+←/→,纵向排列用上下箭头和 Alt+↑/↓,窗口宽度变化时自动适配。完成后收起整理控件,顺序按当前分类保存在本机,也可恢复默认顺序。分组看板和分类瀑布流支持小、中、大列宽;搜索期间暂停整理,详细表格不提供分类排序入口。
-- 在“外观 → 外观方案”输入名称保存当前主题、图标、布局、密度、动效参数和专注状态;已有方案可应用、更新或删除。自动主题与跟随系统的动态效果仍保持自动,方案仅存于当前浏览器,不包含书签数据。
+- 在“外观 → 外观方案”输入名称保存当前主题、图标、布局、密度、动效参数和专注状态;已有方案可应用、更新或删除。自动主题与跟随系统的动态效果仍保持自动,方案仅存于当前浏览器,不包含书签数据。其他版本保存、当前版本无法使用的方案(例如引用了已移除的主题)会被隐藏并原样保留,不影响其余方案。
- 右击书签、按 Shift+F10,或点击卡片上的“更多操作”,可在新标签页打开、复制网址、查看完整名称与所属分类;触屏设备直接显示更多按钮。复制受限时会显示可手动复制的网址。
### Windows
@@ -55,6 +55,8 @@ Chrome / Edge / Safari 书签同步 · 多主题与十六种布局 · 动态天
点击右上角“同步书签”,选择浏览器,再点击“确认同步”。Windows 会检索有有效书签的 Chrome、Edge、Brave、Vivaldi、Opera、Opera GX、QQ、360 极速/极速 X、搜狗高速、夸克和 UC 浏览器;macOS 支持 Chrome、Safari。其他浏览器或使用专有收藏格式的版本,请先在书签管理中导出 HTML 文件,再选择“其他浏览器”并在弹出的窗口中选取该文件。默认识别当前浏览器类型,不支持或无法识别时需要手动选择,不会擅自切换同步来源。
+导入 HTML 时按标签解析,整份文件写在一行、属性顺序不同或标题跨行的导出文件也能识别;未识别到任何书签时不会替换现有主页。重复网址只在页面中显示一次,`data/bookmarks.html` 保持导入时的原样。
+
同步会替换**当前运行目录**内主页的书签和分类,不修改浏览器里的收藏夹;完成后刷新主页并回到“全部”,避免旧分类筛选隐藏新书签,其他外观设置保持不变。多账号按浏览器最近使用的配置读取,网页无法识别当前标签页所属账号。取消不执行同步,同步过程中禁止重复提交;文件模式和远程托管演示页不支持此入口,请通过本地快捷方式打开。
### 安装版与源码版同时使用
@@ -130,6 +132,7 @@ Bookmark/
## 天气刷新
- 天气数据来自 Open-Meteo,打开或刷新网页时立即获取;之后每分钟检查一次,天气满 10 分钟才重新获取。
+- 主天气源失败或 1.5 秒内没有结果时,才同时请求本地服务的备用源;任一来源成功后取消其余请求。本地服务对同一城市的查询结果保留 5 分钟,同时打开的多个页面共用一次上游请求。
- 点击天气旁的刷新按钮可立即更新;点击天气文字可设置或切换城市。
- 最近 10 分钟的天气由各标签页共用:一个页面刷新后,其他页面直接显示结果,不重复请求。打开网页和手动刷新始终重新获取;网络失败时保留已有天气,刷新按钮提示重试。
- 页面在后台时不定时请求,切回页面后如天气已过期会立即补上。浏览器休眠或冻结页面时,检查可能延后;刷新网页或手动点击可立即重新获取。
@@ -149,7 +152,7 @@ Bookmark/
- ZIP 通道从公开 API 读取 `main` 对应的 `APP_VERSION`,仅安装更高的三段版本号,不自动降级。发布 ZIP 更新时必须提高 `scripts/manage.py` 与 `web/index.html` 的版本号;同版本号的代码修改只会被 Git 通道检测到。
- Gitee 的 ZIP 下载 API 需要授权,因此免 Git 升级使用公开文件清单和原始文件接口,按固定提交下载有变化的程序文件;每个文件都校验大小和 Git blob 哈希,Gitee 下载失败时只切到 GitHub 的同一提交,不混装不同版本。请求保持 HTTPS 证书校验,不使用浏览器登录状态。
- ZIP 通道仅更新 `web/`、`scripts/`、`assets/`、`launchers/`、公开示例书签及说明文件。`web/data.js`、私人书签、窗口状态、快捷方式及更新清单之外的额外文件不会被覆盖或删除;浏览器外观设置保持原地址、原端口存储。
-- 替换前先完整下载校验,并将旧程序文件备份到 `data/.update-backups/`。替换失败时恢复已替换文件并保留备份;下载期间本地程序文件被修改则取消升级。ZIP 安装无法像 Git 一样判断原有代码修改,确认升级时会提示自改程序文件将被替换并备份。
+- 替换前先完整下载校验,并将旧程序文件备份到 `data/.update-backups/`,升级成功后只保留最近 5 份。替换失败时恢复已替换文件并保留备份;下载期间本地程序文件被修改则取消升级。ZIP 安装无法像 Git 一样判断原有代码修改,确认升级时会提示自改程序文件将被替换并备份。
- Windows 正在使用的便携 Python 不做在线替换:若新版要求不同的运行时文件,会在替换程序前停止并提示下载完整安装包;macOS/Linux 保持系统 Python 不变。
- 在线升级必须通过本地服务打开页面,直接双击 HTML 不支持。旧 ZIP 若尚未包含免 Git 升级模块,需要先换用包含该功能的新安装包。
- 多页面检查和升级串行执行,Git 通道合并、ZIP 通道下载均固定为本次检查得到的完整提交号;升级完成后沿用原端口自动重启及页面就绪检测。
@@ -216,11 +219,12 @@ runtime\python\python.exe -X utf8 scripts/manage.py
- `data/bookmarks.html` 和 `web/data.js` 包含私人书签,已被 Git 忽略,不能使用 `git add -f` 强制提交。
- `web/data.js` 是本地生成文件,不建议手工编辑;公开示例位于 `data/bookmarks.example.html` 和 `web/data.example.js`。
-- `data/.update-backups/` 为 ZIP 升级的本地程序备份,不会被提交或由升级自动删除。
+- `data/.update-backups/` 为 ZIP 升级的本地程序备份,不会被提交;每次升级成功后只保留最近 5 份。
+- 打开快捷方式时,只在书签源文件或程序版本变化后才重新生成 `web/data.js`;内容没有变化时不改写文件,浏览器缓存保持有效。生成记录保存在 `data/.data-build.json`。
- 以 `javascript:`、`data:` 开头的书签脚本在主页中只显示、不可点击,避免在本地主页内运行脚本。
- 普通打开“书签”快捷方式或 `Bookmark.app` 不会同步浏览器;在主页确认同步或执行对应的手动同步脚本、命令时才会更新。
-- `Bookmark.app` 每次打开都会使用当前的 UI 和 `web/data.js`;如果书签源文件更新较晚,会先重新生成页面数据。
+- `Bookmark.app` 每次打开都会使用当前的 UI 和 `web/data.js`;如果书签源文件更新较晚,会先重新生成页面数据。macOS 安装版只在应用更新后才把页面文件复制到 `~/Library/Application Support/Bookmark`。
- 手动同步只读取浏览器本机缓存,不会修改浏览器内的书签;同步结果会覆盖 `data/bookmarks.html` 并保留为下次打开时的本地快照。
-- 页面默认端口是 `8765`。本地服务只响应 `127.0.0.1` 或 `localhost` 地址,自定义域名指向本机也会被拒绝;同步、升级、保存窗口大小和切换快捷方式图标只接受本主页发起的请求,其他网站无法触发。
+- 页面默认端口是 `8765`。本地服务只响应 `127.0.0.1` 或 `localhost` 地址,自定义域名指向本机也会被拒绝;同步、升级、保存窗口大小和切换快捷方式图标只接受本主页发起的请求,其他网站无法触发。其他网站也不能通过 `'
+ '