diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md index f9a305e..4a7afd8 100644 --- a/PROJECT_CONTEXT.md +++ b/PROJECT_CONTEXT.md @@ -8,11 +8,12 @@ This is a **public, sanitized handoff** for the plugin source repository, not a ### Purpose and current baseline -GitHub Vault Sync is an Obsidian **desktop** plugin that synchronizes a vault to a **separate private GitHub repository** through local Git and Git LFS. Its design goal is multi-device continuity without silently deciding which device's notes win. The public plugin repository is **not** the destination for anyone's notes. v0.2.0 is the latest published software release in this handoff; later documentation commits do not make a new plugin release. +GitHub Vault Sync is an Obsidian **desktop** plugin that synchronizes a vault to a **separate private GitHub repository** through local Git and Git LFS. Its design goal is multi-device continuity without silently deciding which device's notes win. The public plugin repository is **not** the destination for anyone's notes. v0.2.1 is the latest published software release in this handoff; later documentation commits do not make a new plugin release. - Save edits, inspect a read-only preview, choose the direction or resolve individual conflicts, then explicitly confirm the operation. Periodic checks notify; they do not upload automatically. - Ordinary vault files and configured attachments use Git/LFS. Sensitive plugin settings are excluded from plaintext commits; when encrypted settings sync is enabled, the private vault repository stores an AES-256-GCM encrypted bundle. The recovery key stays local and needs a separate safe backup. -- Media optimization in v0.2.0 only **suggests** manual conversion commands. It does not convert, delete, relink, or upload media on its own. Remote repository delete-and-recreate is a separate destructive, confirmation-gated action; it is never part of routine sync. +- Media optimization introduced in v0.2.0 only **suggests** manual conversion commands. It does not convert, delete, relink, or upload media on its own. Remote repository delete-and-recreate is a separate destructive, confirmation-gated action; it is never part of routine sync. +- Windows filename repair introduced in v0.2.1 is also confirmation-gated: it converts forbidden characters to full-width equivalents through Obsidian's rename API, updates links, checks collisions and writes a rollback log before re-previewing. ### Where to work @@ -22,10 +23,11 @@ GitHub Vault Sync is an Obsidian **desktop** plugin that synchronizes a vault to | Preview, selected-note scope and sync orchestration | `src/sync-preview.ts`, `src/current-note.ts`, `src/sync-engine.ts` | | Local Git/LFS and GitHub API | `src/git.ts`, `src/github.ts`, `src/migration.ts` | | Path rules, sensitive-content boundaries and encrypted recovery | `src/policy.ts`, `src/utils.ts`, `src/encrypted-bundle.ts` | +| Windows filename repair planning | `src/path-repair.ts` | | Media suggestions and guarded remote rebuild | `src/media-scan.ts`, `src/rebuild.ts` | -| Tests, package and user-facing limits | `tests/`, `scripts/release_archive.py`, `docs/VALIDATION_v0.2.0.md` | +| Tests, package and user-facing limits | `tests/`, `scripts/release_archive.py`, `docs/VALIDATION_v0.2.1.md` | -See [development history](docs/PROJECT_HISTORY.md) for the **edited public chronology**, [release notes](docs/RELEASE_NOTES_v0.2.0.md) for shipped changes, and [installation instructions](docs/install/README.md) for the currently published package. The three [UI-motion examples](README.md#ui-motion-tour-v013) contain representative sample data; they are not recordings of a real user vault. +See [development history](docs/PROJECT_HISTORY.md) for the **edited public chronology**, [release notes](docs/RELEASE_NOTES_v0.2.1.md) for shipped changes, and [installation instructions](docs/install/README.md) for the currently published package. The three [UI-motion examples](README.md#ui-motion-tour-v013) contain representative sample data; they are not recordings of a real user vault. ### Reproduce and change safely @@ -36,11 +38,11 @@ pnpm test pnpm build ``` -The build outputs an ignored root `main.js`. Run tests with **temporary/synthetic vaults and local bare Git repositories**, not a user's working vault. The release ZIP is allowlisted (`manifest.json`, `main.js`, `styles.css`, and two standalone install guides); `python scripts/release_archive.py verify` checks the existing v0.2.0 archive **against v0.2.0's original sources**. Once inputs change, do not re-label or overwrite that published ZIP or tag: prepare a new version and verify its own package, CI, and download. +The build outputs an ignored root `main.js`. Run tests with **temporary/synthetic vaults and local bare Git repositories**, not a user's working vault. The release ZIP is allowlisted (`manifest.json`, `main.js`, `styles.css`, and two standalone install guides); `python scripts/release_archive.py verify` checks the existing v0.2.1 archive **against v0.2.1's original sources**. Once inputs change, do not re-label or overwrite that published ZIP or tag: prepare a new version and verify its own package, CI, and download. ### Known boundaries and useful next checks -- At the v0.2.0 release, TypeScript, 23 test files / 101 tests, macOS and Windows CI, ZIP byte checks, and selected macOS Obsidian 1.14.4 host interactions passed. **Native Windows Obsidian GUI acceptance was deferred**; Windows CI is not a GUI test. Refer to [validation details](docs/VALIDATION_v0.2.0.md) rather than transferring those results to a future commit. +- At the v0.2.1 release, TypeScript, 24 test files / 103 tests, the production build and selected macOS Obsidian 1.15.0 host interactions passed. **Native Windows Obsidian GUI acceptance was deferred**; Windows CI is not a GUI test. Refer to [validation details](docs/VALIDATION_v0.2.1.md) rather than transferring those results to a future commit. - After an actual private-vault repository rebuild, another device must first back up its old clone, clone again, hydrate LFS objects, and verify its plugin binding and recovery key. Publishing the plugin does not perform this work. - Further engineering candidates already described in [development history](docs/PROJECT_HISTORY.md#6-已知边界与后续开发建议) include checking that backups of conflicted LFS attachments contain hydrated data, and improving recovery ergonomics for simultaneous encrypted-settings changes. These are **not shipped promises**. - Never commit local `data.json`, vault notes, credential material, full cache or logs, absolute home paths, company/customer artifacts, original conversation exports, screenshots of real data, or private design/source assets. A synthetic test fixture is not evidence that a real vault is safe to upload. Preserve the preview/confirmation, remote-change rejection, conflict backup and secret-exclusion tests when changing sync behavior. @@ -49,20 +51,21 @@ The build outputs an ignored root `main.js`. Run tests with **temporary/syntheti ### 初衷与当前基线 -GitHub Vault Sync 是用本机 Git/Git LFS 将 Obsidian **桌面端** Vault 同步至**单独的私有 GitHub 仓库**的插件。设计初衷是多设备持续使用,同时不替用户静默决定哪台设备的笔记优先。**公开插件源码仓库绝不是笔记数据仓库。** 本交接以已发布 v0.2.0 为软件基线;后续只改文档不产生新插件版本。 +GitHub Vault Sync 是用本机 Git/Git LFS 将 Obsidian **桌面端** Vault 同步至**单独的私有 GitHub 仓库**的插件。设计初衷是多设备持续使用,同时不替用户静默决定哪台设备的笔记优先。**公开插件源码仓库绝不是笔记数据仓库。** 本交接以已发布 v0.2.1 为软件基线;后续只改文档不产生新插件版本。 - 先保存修改、只读预览、选择方向或逐项处理冲突,最后明确确认;定时检查只提醒、不自动上传。 - 常规笔记及附件通过 Git/LFS;敏感插件配置不明文提交。启用加密配置同步后,私有 Vault 仓库只保存 AES-256-GCM 密文包,恢复密钥留在本机并须另做安全备份。 -- v0.2.0 媒体优化仅建议手动转换命令,不自动转换、删除、修改笔记链接或上传。远端删库同名重建为独立的高风险确认流程,不属于常规同步。 +- v0.2.0 引入的媒体优化仅建议手动转换命令,不自动转换、删除、修改笔记链接或上传。远端删库同名重建为独立的高风险确认流程,不属于常规同步。 +- v0.2.1 引入的 Windows 文件名修复同样需要用户确认:通过 Obsidian 重命名 API 修改文件并更新链接,检查重名并保存恢复日志,然后重新预览。 ### 源码入口与验证 -上方模块表适用于两种语言。阅读顺序:本交接 → [公开脱敏开发历程](docs/PROJECT_HISTORY.md) → [v0.2.0 验证范围](docs/VALIDATION_v0.2.0.md) → 对应源码和测试。安装与版本变化见[独立中文安装指南](docs/install/README.zh-CN.md)及[发布说明](docs/RELEASE_NOTES_v0.2.0.md)。README 中的界面动效是示意数据,并非真实笔记库录屏。 +上方模块表适用于两种语言。阅读顺序:本交接 → [公开脱敏开发历程](docs/PROJECT_HISTORY.md) → [v0.2.1 验证范围](docs/VALIDATION_v0.2.1.md) → 对应源码和测试。安装与版本变化见[独立中文安装指南](docs/install/README.zh-CN.md)及[发布说明](docs/RELEASE_NOTES_v0.2.1.md)。README 中的界面动效是示意数据,并非真实笔记库录屏。 -按上方四条命令完成依赖安装、类型检查、测试和构建。**只在合成测试库和临时本地 Git 裸仓库**验证;不要以真实工作 Vault 跑回归。根目录构建产物 `main.js` 被忽略。v0.2.0 ZIP 是固定白名单包,既有包的核验针对原发布输入;变更源码或安装说明后须另起版本,不覆盖旧 Tag/附件,也不要把旧 CI 结果当作新提交的验证。 +按上方四条命令完成依赖安装、类型检查、测试和构建。**只在合成测试库和临时本地 Git 裸仓库**验证;不要以真实工作 Vault 跑回归。根目录构建产物 `main.js` 被忽略。v0.2.1 ZIP 是固定白名单包,既有包的核验针对原发布输入;变更源码或安装说明后须另起版本,不覆盖旧 Tag/附件,也不要把旧 CI 结果当作新提交的验证。 ### 后续开发边界 -v0.2.0 已有类型检查、23 个文件/101 项测试、macOS/Windows CI、安装包字节检查和部分 macOS Obsidian 1.14.4 宿主验收;**Windows 原生 Obsidian GUI 尚未验收**。真实私有仓库重建后的第二设备需先备份旧克隆、重新克隆、拉取 LFS,并核对绑定和恢复密钥;插件发布不能替代这一步。LFS 冲突附件备份是否已水合、两设备同时改动加密配置的恢复体验,是[历史文档](docs/PROJECT_HISTORY.md#6-已知边界与后续开发建议)中的候选方向,不是现有保证。 +v0.2.1 已有类型检查、24 个文件/103 项测试、生产构建和部分 macOS Obsidian 1.15.0 宿主验收;**Windows 原生 Obsidian GUI 尚未验收**。真实私有仓库重建后的第二设备需先备份旧克隆、重新克隆、拉取 LFS,并核对绑定和恢复密钥;插件发布不能替代这一步。LFS 冲突附件备份是否已水合、两设备同时改动加密配置的恢复体验,是[历史文档](docs/PROJECT_HISTORY.md#6-已知边界与后续开发建议)中的候选方向,不是现有保证。 不要把本机 `PROJECT.md`、私有 `AGENTS.md`、Vault 笔记、`data.json`、缓存/日志、令牌/恢复密钥、本机绝对路径、公司及客户资料、原始聊天/真实截图或工程素材提交到公开仓库。修改同步流程时要保留预览确认、远端变化拒绝旧授权、冲突备份与明文敏感文件排除的测试。**阅读本文件不构成操作用户 Vault 或发布新版本的授权。** diff --git a/README.md b/README.md index b10462f..13405c3 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ [English](README.md) · [简体中文](README.zh-CN.md) -[![Desktop CI](https://github.com/luoboruodai/github-vault-sync/actions/workflows/ci.yml/badge.svg)](https://github.com/luoboruodai/github-vault-sync/actions/workflows/ci.yml) · [Latest published release: v0.2.0](https://github.com/luoboruodai/github-vault-sync/releases/tag/v0.2.0) +[![Desktop CI](https://github.com/luoboruodai/github-vault-sync/actions/workflows/ci.yml/badge.svg)](https://github.com/luoboruodai/github-vault-sync/actions/workflows/ci.yml) · [Latest published release: v0.2.1](https://github.com/luoboruodai/github-vault-sync/releases/tag/v0.2.1) > **Preview first. Decide what syncs. Keep your notes in your own private repository.** > > An Obsidian desktop plugin for multi-device GitHub + Git LFS vault sync, with conflict review and optional encrypted settings backup. This **public source repository** is not a destination for your notes or credentials. -[**Download v0.2.0 installation ZIP**](https://github.com/luoboruodai/github-vault-sync/releases/download/v0.2.0/github-vault-sync-0.2.0.zip) · [Install / upgrade guide](docs/install/README.md) · [Release notes](docs/RELEASE_NOTES_v0.2.0.md) · [Developer handoff](PROJECT_CONTEXT.md) +[**Download v0.2.1 installation ZIP**](https://github.com/luoboruodai/github-vault-sync/releases/download/v0.2.1/github-vault-sync-0.2.1.zip) · [Install / upgrade guide](docs/install/README.md) · [Release notes](docs/RELEASE_NOTES_v0.2.1.md) · [Developer handoff](PROJECT_CONTEXT.md) **Requires:** Obsidian desktop 1.13.0+, Git, and Git LFS for matching attachments. Windows and macOS are supported; Linux desktop is also supported. Mobile is not supported. The published ZIP is a manual install; GitHub's automatic “Source code” archives are **not** installable plugins. @@ -21,16 +21,17 @@ - **Review before writing:** see notes, attachments, settings and other files in a read-only preview; choose a direction or resolve conflicts before confirming. - **Keep control:** periodic checks notify but never silently upload. Changes after preview invalidate the old approval. - **Protect sensitive settings:** exclude plaintext credentials and, when enabled, back up exceptional settings as an encrypted bundle in your **private** vault repository; store the recovery key separately. -- **Plan media savings:** v0.2.0 suggests guarded, manual conversion commands; it does not convert files or change note links for you. +- **Plan media savings:** v0.2.1 suggests guarded, manual conversion commands; it does not convert files or change note links for you. +- **Repair unsafe filenames explicitly:** when a preview finds Windows-incompatible names, v0.2.1 offers a confirmation dialog; approved repairs use Obsidian's rename API, update links, check collisions and save rollback logs before re-previewing. -**Latest published software: v0.2.0 (October 16, 2026).** [Validation and limitations](docs/VALIDATION_v0.2.0.md) include deferred native Windows Obsidian GUI acceptance. Interface examples below use sample data rather than recordings of a real vault. +**Latest published software: v0.2.1 (November 12, 2026).** [Validation and limitations](docs/VALIDATION_v0.2.1.md) include deferred native Windows Obsidian GUI acceptance. Interface examples below use sample data rather than recordings of a real vault. -## Install the v0.2.0 release +## Install the v0.2.1 release -v0.2.0 retains preview-first synchronization and adds media optimization guidance, five independent settings pages, safer batch-copy interactions, and a guarded remote repository rebuild workflow. See the [release notes](docs/RELEASE_NOTES_v0.2.0.md) and [standalone install/upgrade guide](docs/install/README.md). +v0.2.1 retains preview-first synchronization and adds a confirmed Windows filename repair flow. See the [release notes](docs/RELEASE_NOTES_v0.2.1.md) and [standalone install/upgrade guide](docs/install/README.md). 1. Back up your Obsidian vault. Install Git; install Git LFS before syncing images or other attachments matched by the plugin's LFS rules. -2. Open this project's GitHub **Releases** page and download `github-vault-sync-0.2.0.zip`. The **only manually uploaded release asset** is this ZIP; the same file is kept in [`releases/github-vault-sync-0.2.0.zip`](releases/github-vault-sync-0.2.0.zip). GitHub’s automatic “Source code (zip/tar.gz)” downloads are **not** installable plugin bundles because the project root omits the compiled `main.js`. +2. Open this project's GitHub **Releases** page and download `github-vault-sync-0.2.1.zip`. The **only manually uploaded release asset** is this ZIP; the same file is kept in [`releases/github-vault-sync-0.2.1.zip`](releases/github-vault-sync-0.2.1.zip). GitHub’s automatic “Source code (zip/tar.gz)” downloads are **not** installable plugin bundles because the project root omits the compiled `main.js`. 3. Save your notes and exit Obsidian normally, then extract the ZIP into `/.obsidian/plugins/`. The resulting files must be: ```text @@ -44,7 +45,7 @@ v0.2.0 retains preview-first synchronization and adds media optimization guidanc Avoid an extra nested `github-vault-sync/github-vault-sync/` directory. The ZIP already contains every plugin file and bilingual setup instructions: no separate Release downloads, Node.js, or pnpm are needed for manual installation. Git and, where applicable, Git LFS remain separate **system prerequisites**. The ZIP contains no tokens, recovery keys, or personal vault data. 4. Open Obsidian again. In **Settings → Community plugins**, enable **GitHub Vault Sync**. Requires Obsidian desktop **1.13.0+** on **Windows or macOS** (Linux desktop is also supported); mobile Obsidian is not supported. The same ZIP is used for both Windows and macOS. -If you already use a local pre-release labeled `0.4.0`, public `0.1.0` was the **first public release**; `0.2.0` continues that public series and is not a downgrade or upgrade of the internal `0.4.0` build. You need not replace your working installation merely to download the public package. Back up your vault before any manual replacement and leave local `data.json` in place. +If you already use a local pre-release labeled `0.4.0`, public `0.1.0` was the **first public release**; `0.2.1` continues that public series and is not a downgrade or upgrade of the internal `0.4.0` build. You need not replace your working installation merely to download the public package. Back up your vault before any manual replacement and leave local `data.json` in place. ## Connect your own private vault repository @@ -60,13 +61,13 @@ Select **Preview and sync** in plugin settings or the Sync Center. Before any up Progress shows verified staging file counts and Git/LFS object progress where Git reports it; otherwise the transfer indicator remains indeterminate rather than inventing a percentage. **Timely checking** notices local changes after a configurable delay (default 90 seconds) and checks GitHub every five minutes. It **never uploads without your explicit preview and confirmation**. -## Media optimization before upload (v0.2.0) +## Media optimization before upload (v0.2.1) -During a full sync preview, v0.2.0 lists included PNG/JPG/JPEG, PDF, MP4, and GIF files larger than the configurable threshold. These suggestions **never block synchronization**, and the plugin does not execute the conversions. The **Media optimization** settings page controls the threshold (default 2 MB), media-only glob exclusions, the image PSNR floor (default 35 dB), and whether estimated savings are shown. +During a full sync preview, v0.2.1 lists included PNG/JPG/JPEG, PDF, MP4, and GIF files larger than the configurable threshold. These suggestions **never block synchronization**, and the plugin does not execute the conversions. The **Media optimization** settings page controls the threshold (default 2 MB), media-only glob exclusions, the image PSNR floor (default 35 dB), and whether estimated savings are shown. Each finding has a **Copy conversion command** action, and **Copy all conversion commands** copies every finding, including entries on later pages. A successful batch copy immediately updates the visible buttons and entries loaded afterward; scrolling to the end loads 30 more findings and updates the displayed count. Run the copied commands from the vault root in a terminal; on Windows use Git Bash. Images require Pillow and `pillow-avif-plugin`, PDFs require Ghostscript, MP4 commands require FFmpeg with `libsvtav1`, and GIF commands require FFmpeg with `libwebp`. PNG starts at AVIF q70 and JPG at q60; the command then tries higher quality steps through q100 until the configured PSNR floor is met. If the floor still cannot be met, it leaves no converted file. Existing destinations are not overwritten, and colliding output names receive a short hash. -PDF commands use Ghostscript `/ebook`, MP4 commands use AV1, and GIF commands produce animated WebP. Estimated savings are projections from the measured vault samples, not guarantees for a particular file. Lossy replacement is irreversible: keep a cold backup, inspect the generated files, update note links yourself, and open a fresh sync preview afterward. AVIF is included in the default Git LFS rules and is added once when migrating pre-0.2.0 settings. +PDF commands use Ghostscript `/ebook`, MP4 commands use AV1, and GIF commands produce animated WebP. Estimated savings are projections from the measured vault samples, not guarantees for a particular file. Lossy replacement is irreversible: keep a cold backup, inspect the generated files, update note links yourself, and open a fresh sync preview afterward. AVIF is included in the default Git LFS rules and is added once when migrating pre-0.2.1 settings. Advanced settings also include a **Remote rebuild checklist**. It only displays the current private repository and the required backup, history-optimization, and second-device pause checks; it never deletes or recreates a repository. @@ -80,7 +81,7 @@ On another desktop device, install this plugin, bind the same private repository ## Cross-platform verification -The macOS/Windows CI runs TypeScript checks, temporary local Git/Git LFS integration tests, and a production build. **Release-tag CI also checks that the ZIP matches the built files; `main`/PR development builds do not require an unreleased ZIP.** This is not a Windows Obsidian GUI session or a guarantee for every third-party theme or Git installation. Separate macOS Obsidian 1.14.4 checks covered the running v0.2.0 version, five settings pages, media preview/copy interactions, safe settings migration, and read-only remote-rebuild confirmation gating. Native Windows Obsidian GUI acceptance remains deferred. See the [validation scope](docs/VALIDATION_v0.2.0.md); report host-specific issues with the Obsidian version, OS, and non-secret diagnostic details. No real vault or GitHub token is used in CI. +The macOS/Windows CI runs TypeScript checks, temporary local Git/Git LFS integration tests, and a production build. **Release-tag CI also checks that the ZIP matches the built files; `main`/PR development builds do not require an unreleased ZIP.** This is not a Windows Obsidian GUI session or a guarantee for every third-party theme or Git installation. Separate macOS Obsidian 1.14.4 checks covered the running v0.2.1 version, five settings pages, media preview/copy interactions, safe settings migration, and read-only remote-rebuild confirmation gating. Native Windows Obsidian GUI acceptance remains deferred. See the [validation scope](docs/VALIDATION_v0.2.1.md); report host-specific issues with the Obsidian version, OS, and non-secret diagnostic details. No real vault or GitHub token is used in CI. ## UI motion tour (v0.1.3) @@ -119,7 +120,7 @@ These v0.1.3 **illustrative UI-motion demos** use the plugin stylesheet and repr ## Share this project -A [short, bilingual project introduction](docs/SHARE.md) is available to share alongside the v0.2.0 installation link. Its interface media uses synthetic examples; do not publish your own vault or credentials when sharing the project. +A [short, bilingual project introduction](docs/SHARE.md) is available to share alongside the v0.2.1 installation link. Its interface media uses synthetic examples; do not publish your own vault or credentials when sharing the project. ## Develop and contribute @@ -134,7 +135,7 @@ pnpm test pnpm build ``` -The build creates `main.js` in the project root; release maintainers can run `python scripts/release_archive.py build` to create and verify the allowlisted ZIP. The ZIP uses the independent bilingual guides under `docs/install/`, not these repository READMEs; its online video/documentation links point to the release tag. Only `manifest.json`, `main.js`, and `styles.css` belong in an installed plugin folder. For the ready-to-install package use the single [release ZIP](releases/github-vault-sync-0.2.0.zip) instead. Start with [PROJECT_CONTEXT.md](PROJECT_CONTEXT.md) for an English/Chinese, privacy-reviewed handoff; use the [public development history](docs/PROJECT_HISTORY.md) for decisions, testing notes and a summarized chronology. Private project notes and raw conversations are not published. +The build creates `main.js` in the project root; release maintainers can run `python scripts/release_archive.py build` to create and verify the allowlisted ZIP. The ZIP uses the independent bilingual guides under `docs/install/`, not these repository READMEs; its online video/documentation links point to the release tag. Only `manifest.json`, `main.js`, and `styles.css` belong in an installed plugin folder. For the ready-to-install package use the single [release ZIP](releases/github-vault-sync-0.2.1.zip) instead. Start with [PROJECT_CONTEXT.md](PROJECT_CONTEXT.md) for an English/Chinese, privacy-reviewed handoff; use the [public development history](docs/PROJECT_HISTORY.md) for decisions, testing notes and a summarized chronology. Private project notes and raw conversations are not published. ## Security note diff --git a/README.zh-CN.md b/README.zh-CN.md index 0d883cd..83a2091 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -2,13 +2,13 @@ [English](README.md) · [简体中文](README.zh-CN.md) -[![桌面端 CI](https://github.com/luoboruodai/github-vault-sync/actions/workflows/ci.yml/badge.svg)](https://github.com/luoboruodai/github-vault-sync/actions/workflows/ci.yml) · [最新正式发行版:v0.2.0](https://github.com/luoboruodai/github-vault-sync/releases/tag/v0.2.0) +[![桌面端 CI](https://github.com/luoboruodai/github-vault-sync/actions/workflows/ci.yml/badge.svg)](https://github.com/luoboruodai/github-vault-sync/actions/workflows/ci.yml) · [最新正式发行版:v0.2.1](https://github.com/luoboruodai/github-vault-sync/releases/tag/v0.2.1) > **先预览,再决定如何同步;笔记始终放在你自己的私有仓库。** > > 用 GitHub 和 Git LFS 跨设备同步 Obsidian 桌面端 Vault 的插件,支持冲突审阅及可选的加密配置备份。这个**公开源码仓库**不是笔记或凭据的存放地。 -[**下载 v0.2.0 安装 ZIP**](https://github.com/luoboruodai/github-vault-sync/releases/download/v0.2.0/github-vault-sync-0.2.0.zip) · [安装/升级指南](docs/install/README.zh-CN.md) · [发布说明](docs/RELEASE_NOTES_v0.2.0.md) · [开发接手指南](PROJECT_CONTEXT.md#简体中文) +[**下载 v0.2.1 安装 ZIP**](https://github.com/luoboruodai/github-vault-sync/releases/download/v0.2.1/github-vault-sync-0.2.1.zip) · [安装/升级指南](docs/install/README.zh-CN.md) · [发布说明](docs/RELEASE_NOTES_v0.2.1.md) · [开发接手指南](PROJECT_CONTEXT.md#简体中文) **运行条件:**Obsidian 桌面端 1.13.0+、Git;同步符合 LFS 规则的附件还需 Git LFS。支持 Windows 和 macOS,也支持 Linux 桌面端;不支持移动端。正式 ZIP 需要手动安装;GitHub 自动生成的 “Source code” 压缩包**不是**可安装插件。 @@ -21,16 +21,17 @@ - **上传前审阅:**先只读预览笔记、附件、配置及其他文件,再选方向或逐项处理冲突,明确确认后才执行。 - **始终由你决定:**定时检查只提醒,绝不静默上传;预览后任一端发生变化会使旧授权失效。 - **保护敏感配置:**禁止明文凭据提交;启用后可将特殊配置加密备份到**私有**笔记仓库,恢复密钥需另行保管。 -- **预先规划媒体体积:**v0.2.0 提供受保护的手动转换命令建议;不会自动转换文件或修改笔记链接。 +- **预先规划媒体体积:**v0.2.1 提供受保护的手动转换命令建议;不会自动转换文件或修改笔记链接。 +- **显式修复不安全文件名:**预览遇到 Windows 不兼容文件名时,v0.2.1 会先弹出确认;只有用户批准后才通过 Obsidian 重命名 API 修改文件并更新链接,同时检查重名并保存可回滚日志,再重新预览。 -**最新正式软件版本:v0.2.0(2026-10-16)。** [验证范围与限制](docs/VALIDATION_v0.2.0.md)包含尚未完成的 Windows 原生 Obsidian GUI 验收。下文界面演示使用示意数据,并非真实笔记库录屏。 +**最新正式软件版本:v0.2.1(2026-11-12)。** [验证范围与限制](docs/VALIDATION_v0.2.1.md)包含尚未完成的 Windows 原生 Obsidian GUI 验收。下文界面演示使用示意数据,并非真实笔记库录屏。 -## 安装 v0.2.0 +## 安装 v0.2.1 -v0.2.0 保留先预览再同步,加入媒体优化建议、五个独立设置页、更稳妥的批量复制交互,以及带多重确认的远程仓库重建流程。详见[更新说明](docs/RELEASE_NOTES_v0.2.0.md)与[独立安装/升级指南](docs/install/README.zh-CN.md)。 +v0.2.1 保留先预览再同步,加入经确认的 Windows 文件名修复流程。详见[更新说明](docs/RELEASE_NOTES_v0.2.1.md)与[独立安装/升级指南](docs/install/README.zh-CN.md)。 1. 先备份 Obsidian Vault。安装 Git;如需同步图片、PDF 等符合附件规则的文件,另需安装 Git LFS。 -2. 打开本项目 GitHub 的 **Releases**,下载 `github-vault-sync-0.2.0.zip`。**唯一手动上传的 Release 资产**就是此 ZIP;仓库也保留同一份 [`releases/github-vault-sync-0.2.0.zip`](releases/github-vault-sync-0.2.0.zip)。GitHub 自动生成的“Source code (zip/tar.gz)”**不是可安装插件包**,因为项目根目录不收录构建后的 `main.js`。 +2. 打开本项目 GitHub 的 **Releases**,下载 `github-vault-sync-0.2.1.zip`。**唯一手动上传的 Release 资产**就是此 ZIP;仓库也保留同一份 [`releases/github-vault-sync-0.2.1.zip`](releases/github-vault-sync-0.2.1.zip)。GitHub 自动生成的“Source code (zip/tar.gz)”**不是可安装插件包**,因为项目根目录不收录构建后的 `main.js`。 3. 保存笔记并正常退出 Obsidian,然后将 ZIP 解压到 `/.obsidian/plugins/`,确认结果为: ```text @@ -44,7 +45,7 @@ v0.2.0 保留先预览再同步,加入媒体优化建议、五个独立设置 不要多套一层 `github-vault-sync/`。ZIP 已含全部插件文件和双语安装说明;手动安装不用再单独下载 Release 资产,也无需 Node.js、pnpm。Git 及按附件规则需要的 Git LFS 仍是**系统层依赖**。安装包不含 Token、恢复密钥或个人笔记。 4. 重新打开 Obsidian,在「设置 → 第三方插件」启用 **GitHub Vault Sync**。适用于 **Windows 和 macOS 的 Obsidian 桌面版 1.13.0+**(也支持 Linux 桌面版);不支持移动端。Windows、macOS 使用同一个 ZIP。 -如果你当前已经安装标为 `0.4.0` 的本地原型,公开 `0.1.0` 才是**首次公开发行**;`0.2.0` 延续这一公开版本系列,不等同于内部 `0.4.0` 的版本降级或升级。无需仅为了下载公开包就替换正常运行的安装;手动替换前请备份 Vault,并保留本地的 `data.json`。 +如果你当前已经安装标为 `0.4.0` 的本地原型,公开 `0.1.0` 才是**首次公开发行**;`0.2.1` 延续这一公开版本系列,不等同于内部 `0.4.0` 的版本降级或升级。无需仅为了下载公开包就替换正常运行的安装;手动替换前请备份 Vault,并保留本地的 `data.json`。 ## 连接自己的私有笔记仓库 @@ -60,13 +61,13 @@ v0.2.0 保留先预览再同步,加入媒体优化建议、五个独立设置 进度条在 Git 能报告文件/对象数量时显示阶段百分比;没有可靠上传计数时显示不确定进度,不假报进度。**及时检查**在本地改动后按设定延迟(默认 90 秒)检查,并每 5 分钟检查远端,**绝不绕过预览自动上传**。 -## 上传前媒体优化(v0.2.0) +## 上传前媒体优化(v0.2.1) -v0.2.0 在整库同步预览中列出超过阈值的已纳入 PNG/JPG/JPEG、PDF、MP4 和 GIF 文件。媒体建议**不会阻塞同步**,插件也不会执行转换。「媒体优化」设置页可调整阈值(默认 2 MB)、仅作用于建议的 glob 排除规则、图像 PSNR 质量下限(默认 35 dB),以及是否显示预估节省。 +v0.2.1 在整库同步预览中列出超过阈值的已纳入 PNG/JPG/JPEG、PDF、MP4 和 GIF 文件。媒体建议**不会阻塞同步**,插件也不会执行转换。「媒体优化」设置页可调整阈值(默认 2 MB)、仅作用于建议的 glob 排除规则、图像 PSNR 质量下限(默认 35 dB),以及是否显示预估节省。 每个文件都提供「复制转换命令」,「一键复制全部转换命令」还会复制全部分页中的命令。批量复制成功后,当前按钮和后续加载的条目会同步显示复制状态;滚动到底部会自动追加 30 项并更新数量。请在 Vault 根目录的终端执行;Windows 使用 Git Bash。图像依赖 Pillow 和 `pillow-avif-plugin`,PDF 依赖 Ghostscript,MP4 需要含 `libsvtav1` 的 FFmpeg,GIF 需要含 `libwebp` 的 FFmpeg。PNG 从 AVIF q70、JPG 从 q60 开始,随后逐档尝试至 q100,直到达到设定的 PSNR 下限;仍不达标则不留下转换文件。已有目标不会被覆盖,输出重名时会追加短哈希。 -PDF 使用 Ghostscript `/ebook`,MP4 使用 AV1,GIF 生成动态 WebP。预估节省来自真实样本的投影系数,不保证单个文件的实际结果。有损替换不可逆:请保留冷备份,检查生成文件,自行更新笔记引用,再重新打开同步预览。AVIF 已加入默认 Git LFS 规则,并会在 0.2.0 之前的配置迁移时补充一次。 +PDF 使用 Ghostscript `/ebook`,MP4 使用 AV1,GIF 生成动态 WebP。预估节省来自真实样本的投影系数,不保证单个文件的实际结果。有损替换不可逆:请保留冷备份,检查生成文件,自行更新笔记引用,再重新打开同步预览。AVIF 已加入默认 Git LFS 规则,并会在 0.2.1 之前的配置迁移时补充一次。 高级设置中另有「远程重建准备」清单。它只显示当前私有仓库及必须完成的冷备份、历史优化和第二设备暂停检查,不执行删除或重建。 @@ -80,7 +81,7 @@ PDF 使用 Ghostscript `/ebook`,MP4 使用 AV1,GIF 生成动态 WebP。预 ## 跨平台验证 -macOS/Windows CI 会运行 TypeScript 检查、临时本地 Git/Git LFS 集成测试和生产构建。**Release 标签 CI 还会核对 ZIP 与构建产物;`main`/PR 开发构建不要求提供未发布版本的 ZIP。**CI 不是 Windows 的 Obsidian 图形界面实机测试,也无法保证适配所有第三方主题或 Git 安装方式。本轮另在 macOS Obsidian 1.14.4 宿主验证 v0.2.0 运行版本、五个设置页、媒体预览/复制交互、配置安全迁移,以及不执行删除的远程重建确认门控;Windows 原生 Obsidian GUI 验收继续延期。详见[验证范围](docs/VALIDATION_v0.2.0.md);遇到平台问题请提供系统、Obsidian 版本及不含密钥的诊断信息。CI 不接触真实笔记库或 GitHub Token。 +macOS/Windows CI 会运行 TypeScript 检查、临时本地 Git/Git LFS 集成测试和生产构建。**Release 标签 CI 还会核对 ZIP 与构建产物;`main`/PR 开发构建不要求提供未发布版本的 ZIP。**CI 不是 Windows 的 Obsidian 图形界面实机测试,也无法保证适配所有第三方主题或 Git 安装方式。本轮另在 macOS Obsidian 1.14.4 宿主验证 v0.2.1 运行版本、五个设置页、媒体预览/复制交互、配置安全迁移,以及不执行删除的远程重建确认门控;Windows 原生 Obsidian GUI 验收继续延期。详见[验证范围](docs/VALIDATION_v0.2.1.md);遇到平台问题请提供系统、Obsidian 版本及不含密钥的诊断信息。CI 不接触真实笔记库或 GitHub Token。 ## UI Motion 界面动效导览(v0.1.3) @@ -119,7 +120,7 @@ macOS/Windows CI 会运行 TypeScript 检查、临时本地 Git/Git LFS 集成 ## 转发项目 -可使用[中英双语可转发简介](docs/SHARE.md#简体中文)及 v0.2.0 安装链接介绍项目。界面素材仅含示意数据;转发时不要附上自己的真实笔记库或凭据。 +可使用[中英双语可转发简介](docs/SHARE.md#简体中文)及 v0.2.1 安装链接介绍项目。界面素材仅含示意数据;转发时不要附上自己的真实笔记库或凭据。 ## 参与开发与反馈 @@ -134,7 +135,7 @@ pnpm test pnpm build ``` -构建产物为项目根目录的 `main.js`;发布者可运行 `python scripts/release_archive.py build` 生成并校验白名单 ZIP。包内采用 `docs/install/` 下的独立中英说明,不直接复制仓库 README;在线动效与文档链接固定到本版标签。安装到 Vault 的只需 `manifest.json`、`main.js`、`styles.css`。现成安装包请使用唯一的 [Release ZIP](releases/github-vault-sync-0.2.0.zip)。开发时先读[中英双语公开接手指南](PROJECT_CONTEXT.md#简体中文),再按需读[公开脱敏开发历程](docs/PROJECT_HISTORY.md)中的决策和验证记录。本机 PROJECT、原始聊天和真实用户数据不公开。 +构建产物为项目根目录的 `main.js`;发布者可运行 `python scripts/release_archive.py build` 生成并校验白名单 ZIP。包内采用 `docs/install/` 下的独立中英说明,不直接复制仓库 README;在线动效与文档链接固定到本版标签。安装到 Vault 的只需 `manifest.json`、`main.js`、`styles.css`。现成安装包请使用唯一的 [Release ZIP](releases/github-vault-sync-0.2.1.zip)。开发时先读[中英双语公开接手指南](PROJECT_CONTEXT.md#简体中文),再按需读[公开脱敏开发历程](docs/PROJECT_HISTORY.md)中的决策和验证记录。本机 PROJECT、原始聊天和真实用户数据不公开。 ## 安全提示 diff --git a/docs/PROJECT_HISTORY.md b/docs/PROJECT_HISTORY.md index ef187d3..ef5ba00 100644 --- a/docs/PROJECT_HISTORY.md +++ b/docs/PROJECT_HISTORY.md @@ -1,10 +1,10 @@ # GitHub Vault Sync|开发历程与后续接手指南(公开脱敏版) -> **首发日期:2026-10-08;当前交付版本:v0.2.0(2026-10-16)。** v0.1.0 仍是首次公开发行;本记录按时间线保留后续兼容、媒体优化与安全重建更新。 本文件为后续开发者准备,按时间线整理需求对话、实际实施、反复发现的缺陷、安全边界与测试。它是**结构化的脱敏会话纪要,不是逐字导出的聊天记录**:公开仓库不收录笔记正文、个人截图、具体本机 Vault 路径、GitHub 身份、访问令牌或恢复密钥。原始对话应仅在私有的 Codex 任务中回看。不要将个人 Vault 或本机 `data.json` 添入此公开仓库。 +> **首发日期:2026-10-08;当前交付版本:v0.2.1(2026-11-12)。** v0.1.0 仍是首次公开发行;本记录按时间线保留后续兼容、媒体优化、安全重建与 Windows 文件名修复更新。 本文件为后续开发者准备,按时间线整理需求对话、实际实施、反复发现的缺陷、安全边界与测试。它是**结构化的脱敏会话纪要,不是逐字导出的聊天记录**:公开仓库不收录笔记正文、个人截图、具体本机 Vault 路径、GitHub 身份、访问令牌或恢复密钥。原始对话应仅在私有的 Codex 任务中回看。不要将个人 Vault 或本机 `data.json` 添入此公开仓库。 ## 0. 仓库定位与版本说明 -- **此公开仓库**:插件源码、测试、英文/中文 README、公开开发记录,以及当前 Release 的单一无凭据安装包 `releases/github-vault-sync-0.2.0.zip`。历史安装包仍保留在对应的 GitHub Release 中。 +- **此公开仓库**:插件源码、测试、英文/中文 README、公开开发记录,以及当前 Release 的单一无凭据安装包 `releases/github-vault-sync-0.2.1.zip`。历史安装包仍保留在对应的 GitHub Release 中。 - **用户自己的私有仓库**:Obsidian Vault 的 Markdown、附件及加密配置。插件强制要求此笔记数据仓库为私有;绝不应把笔记或凭据放进公开插件项目仓库。 - 项目先以内部原型版本号 `0.1.x → 0.2.x → 0.3.x → 0.4.0` 迭代;应用户要求,以现有功能基线重新编号为**第一个公开发行版 v0.1.0**。发布资产 `manifest.json`、`package.json`、`versions.json`、Tag 和 ZIP 都应一致。内部已安装的 `0.4.0` 与公开首版 `0.1.0` 的编号并非“升级”关系,不能依赖 Obsidian 按版本号自动覆盖;不要为了首版发布无故替换正在工作的 Vault 插件。 @@ -204,3 +204,10 @@ pnpm build - 高级设置提供只读远程重建清单;「账户与仓库」的执行入口增加远端/权限/所有者预检、Vault 外 bundle 验证、精确仓库名和多项危险确认。删除后的重建或推送失败不会自动重试删除,相关状态机与本地裸仓库强推均有回归测试。 - 最终实现基线通过 TypeScript、23 个测试文件/101 项测试、生产构建和 macOS/Windows CI;macOS Obsidian 1.14.4 覆盖五页、媒体交互与只读重建门控验收。Windows Git Bash 样本命令由 CI 实际执行,但 **Windows 原生 Obsidian GUI 验收继续延期**。 - 远程删库同名重建后,第二设备必须先备份旧克隆、重新克隆并执行 `git lfs pull`,核对插件绑定与恢复密钥后再恢复同步。发布说明、独立双语指南与[验证记录](VALIDATION_v0.2.0.md)同步保留该边界;发布经 PR、标签 CI 和附件下载回读完成,不移动旧标签。 + +## v0.2.1 确认式 Windows 文件名修复(2026-11-12) + +- 用户连续遇到新增笔记标题含 `?`、`:`、`|` 等 Windows 禁止字符,导致同步预览被跨平台校验阻断;本机手工重命名只能处理当批文件,不能作为插件长期能力。 +- 新增独立的文件名转换与修复规划模块:将禁用字符替换为全角等价字符,拒绝空操作、重复目标和已存在目标;同步预览发现此类路径时提供「修复并重新预览」确认弹窗。 +- 修复只在用户批准后执行,使用 Obsidian `fileManager.renameFile` 更新文件和链接,保存本机回滚日志;失败时反向恢复,成功后重新扫描并继续独立的同步前确认,不自动上传或合并。 +- 实现通过 TypeScript、24 个测试文件/103 项测试、生产构建和固定白名单 ZIP 校验;macOS Obsidian 1.15.0 实机验收覆盖确认弹窗、Obsidian 重命名、链接检查、恢复日志、远端只读检查和同步预览。Windows 原生 Obsidian GUI 验收继续延期。 diff --git a/docs/RELEASE_NOTES_v0.2.1.md b/docs/RELEASE_NOTES_v0.2.1.md new file mode 100644 index 0000000..343ad8f --- /dev/null +++ b/docs/RELEASE_NOTES_v0.2.1.md @@ -0,0 +1,55 @@ +# GitHub Vault Sync v0.2.1 — Confirmed Windows filename repair + +> **2026-11-12 · v0.2.1 release.** One installable ZIP for desktop Obsidian; preserve local settings when upgrading. Native Windows Obsidian GUI acceptance remains deferred and is not claimed as passed. See the [validation record](VALIDATION_v0.2.1.md). + +## 中文 + +v0.2.1 保留 v0.2.0 的“先预览、再确认同步”和跨平台安全校验,并加入**需用户确认的 Windows 不兼容文件名修复流程**,避免为安全同步手工重命名后仍反复遇到同类阻断。 + +### 新增与改进 + +- 当同步预览检测到 `?`、`:`、`"`、`|` 等 Windows 禁止文件名时,插件不再只显示错误;它提供「修复并重新预览」确认弹窗。 +- 修复使用 Obsidian `fileManager.renameFile`,由 Obsidian 更新笔记和附件链接;不绕过编辑器直接改文件系统。 +- 执行前会拒绝空操作、重复目标及已存在目标;执行后重新扫描,只有路径、大小写冲突和嵌套 Git 检查全部通过才会继续生成预览。 +- 每次已完成的重命名都会写入本机 `github-vault-sync-backups/path-repairs/` 下的恢复日志;失败时自动反向恢复已完成项,并报告日志位置。 +- 修复只修改本机文件名和链接,不提交、不上传、不合并。随后仍会显示独立的「同步前确认」。 + +### 安全边界 + +跨平台路径校验仍然保留。v0.2.1 不会允许 Windows 禁止路径进入 Git,也不会在后台自动改名。用户取消修复时,插件不修改任何笔记;用户批准修复后,也仍需在同步前确认中选择方向和上传。 + +### 验证状态 + +- 本机 TypeScript 检查、24 个测试文件/103 项测试和生产构建通过;新增文件名转换与冲突规划回归测试。 +- macOS Obsidian 1.15.0 宿主验收覆盖插件加载、修复确认弹窗、Obsidian 重命名、链接检查、远端只读检查与同步前确认。 +- Windows CI 不等于 Windows 原生 Obsidian GUI;本版原生 Windows GUI 验收继续延期。 + +### 安装/升级 + +下载本次 Release 唯一的手动附件 `github-vault-sync-0.2.1.zip`,不要使用自动生成的 Source code ZIP。保存笔记、备份 Vault 后正常退出 Obsidian;只替换 `.obsidian/plugins/github-vault-sync/` 中的三个运行文件,**保留现有本机 `data.json`**。详细步骤见[独立中文指南](install/README.zh-CN.md),ZIP 也附有同一份中英说明。 + +## English + +v0.2.1 keeps the v0.2.0 preview-first sync and cross-platform path validation, and adds a **confirmation-gated repair flow for Windows-incompatible filenames** so users no longer need to rename notes manually after the preview is blocked. + +### New and improved + +- When a preview detects `?`, `:`, `"`, `|` or another Windows-forbidden filename, the plugin now offers a **Repair and preview again** confirmation instead of only reporting the error. +- Repairs use Obsidian's `fileManager.renameFile`, so notes and attachment links are updated by Obsidian rather than by direct filesystem edits. +- No-op, duplicate and already-existing targets are rejected before changes. A post-repair scan must pass path, case-collision and nested-Git checks before preview generation continues. +- Completed renames are written to a local rollback log under `github-vault-sync-backups/path-repairs/`; failures restore completed renames and report the log location. +- Repair changes only local filenames and links. It never commits, uploads or merges; a separate **Sync preview confirmation** still follows. + +### Safety boundaries + +The cross-platform path validation remains in place. v0.2.1 never stages Windows-forbidden paths and never renames in the background. Cancelling the repair leaves notes untouched; after an approved repair, sync still requires the usual preview and confirmation. + +### Validation status + +- Local TypeScript checks, 24 test files / 103 tests and the production build passed; new regression tests cover filename conversion and repair planning. +- macOS Obsidian 1.15.0 host acceptance covered plugin loading, the repair confirmation dialog, Obsidian renaming, link checks, read-only remote checking and the sync preview. +- Windows CI is not native Windows Obsidian GUI acceptance; native Windows GUI validation remains deferred. + +### Installation / upgrade + +Download the single manual asset `github-vault-sync-0.2.1.zip`, not GitHub's automatic Source code archive. Save notes, back up the vault and exit Obsidian normally. Replace only the three runtime files in `.obsidian/plugins/github-vault-sync/` and **keep the existing local `data.json`**. The ZIP includes the same [standalone English guide](install/README.md) and its Chinese counterpart. diff --git a/docs/SHARE.md b/docs/SHARE.md index 477f541..1d1bd4d 100644 --- a/docs/SHARE.md +++ b/docs/SHARE.md @@ -2,7 +2,7 @@ [English](#english) · [简体中文](#简体中文) -These short introductions are for sharing the **existing v0.2.0 release**, not for announcing a new version. Use the linked public illustration or the [sample-data motion demos](../README.md#ui-motion-tour-v013); neither is a recording of a user's vault. Do not substitute personal notes, repository names, recovery keys, or real diagnostic logs in promotional material. +These short introductions are for sharing the **existing v0.2.1 release**, not for announcing a new version. Use the linked public illustration or the [sample-data motion demos](../README.md#ui-motion-tour-v013); neither is a recording of a user's vault. Do not substitute personal notes, repository names, recovery keys, or real diagnostic logs in promotional material. ## English @@ -12,9 +12,9 @@ These short introductions are for sharing the **existing v0.2.0 release**, not f **Shareable introduction** -> GitHub Vault Sync helps you keep an Obsidian desktop vault across devices using local Git, Git LFS, and a separate private GitHub repository. See local and remote changes before syncing, choose how conflicts are handled, and confirm the result yourself—periodic checks do not upload in the background. v0.2.0 also offers optional media optimization advice and encrypted settings backup. Download the manually installable ZIP, not GitHub's automatic source archive. Windows and macOS are supported; native Windows Obsidian GUI acceptance is still pending. +> GitHub Vault Sync helps you keep an Obsidian desktop vault across devices using local Git, Git LFS, and a separate private GitHub repository. See local and remote changes before syncing, choose how conflicts are handled, and confirm the result yourself—periodic checks do not upload in the background. v0.2.1 also offers optional media optimization advice and encrypted settings backup. Download the manually installable ZIP, not GitHub's automatic source archive. Windows and macOS are supported; native Windows Obsidian GUI acceptance is still pending. -[Project and bilingual setup](../README.md) · [v0.2.0 installation ZIP](https://github.com/luoboruodai/github-vault-sync/releases/download/v0.2.0/github-vault-sync-0.2.0.zip) · [Validation scope](VALIDATION_v0.2.0.md) +[Project and bilingual setup](../README.md) · [v0.2.1 installation ZIP](https://github.com/luoboruodai/github-vault-sync/releases/download/v0.2.1/github-vault-sync-0.2.1.zip) · [Validation scope](VALIDATION_v0.2.1.md) ## 简体中文 @@ -24,6 +24,6 @@ These short introductions are for sharing the **existing v0.2.0 release**, not f **可转发介绍** -> GitHub Vault Sync 通过本机 Git、Git LFS 与独立的私有 GitHub 仓库,帮助跨设备使用 Obsidian 桌面笔记库。同步前先看本机和远端的变化,选择冲突处理方式,再由自己确认;定时检查不会后台自动上传。v0.2.0 还提供可选的媒体优化建议与加密配置备份。安装请下载可直接解压的 ZIP,而不是 GitHub 自动生成的源码包。支持 Windows、macOS;Windows 原生 Obsidian GUI 验收仍待补充。 +> GitHub Vault Sync 通过本机 Git、Git LFS 与独立的私有 GitHub 仓库,帮助跨设备使用 Obsidian 桌面笔记库。同步前先看本机和远端的变化,选择冲突处理方式,再由自己确认;定时检查不会后台自动上传。v0.2.1 还提供可选的媒体优化建议与加密配置备份。安装请下载可直接解压的 ZIP,而不是 GitHub 自动生成的源码包。支持 Windows、macOS;Windows 原生 Obsidian GUI 验收仍待补充。 -[项目与中文安装说明](../README.zh-CN.md) · [v0.2.0 安装 ZIP](https://github.com/luoboruodai/github-vault-sync/releases/download/v0.2.0/github-vault-sync-0.2.0.zip) · [验证范围](VALIDATION_v0.2.0.md) +[项目与中文安装说明](../README.zh-CN.md) · [v0.2.1 安装 ZIP](https://github.com/luoboruodai/github-vault-sync/releases/download/v0.2.1/github-vault-sync-0.2.1.zip) · [验证范围](VALIDATION_v0.2.1.md) diff --git a/docs/VALIDATION_v0.2.1.md b/docs/VALIDATION_v0.2.1.md new file mode 100644 index 0000000..ac31adc --- /dev/null +++ b/docs/VALIDATION_v0.2.1.md @@ -0,0 +1,46 @@ +# GitHub Vault Sync v0.2.1 — Validation record + +**Date:** 2026-11-12 +**Scope:** confirmed Windows filename repair, preview-first sync, release packaging and desktop-host loading. + +## 中文 + +### 本次范围 + +v0.2.1 只改变“预览发现 Windows 不兼容路径后的处理”:保留原有阻断和跨平台规则,在用户点击「修复并重新预览」后才调用 Obsidian 重命名 API、更新链接、保存恢复日志并重新扫描。不会放宽 Git 暂存规则,不会在后台自动改名,也不会新增上传授权。 + +### 已执行验证 + +- TypeScript:`tsc --noEmit` 通过。 +- 测试:24 个测试文件、103 项测试全部通过;新增 `path-repair` 回归测试覆盖禁用字符转换、尾部标点、保留设备名、重复目标和已存在目标。 +- 构建:esbuild 生产构建通过。 +- macOS Obsidian 1.15.0:插件加载、同步中心状态、修复确认弹窗、Obsidian 重命名、链接检查、远端只读检查和同步前确认均由真实宿主复核;验收中实际修复 1 个临时测试文件和 4 个并行任务新增的真实文件,回滚日志完整,随后删除临时文件。 +- 发布包:固定白名单 ZIP、源码/构建字节一致性和独立中英安装指南由 `scripts/release_archive.py` 校验。 + +### 边界与限制 + +- 修复弹窗需要用户确认;插件不会自动改名。转换规则把 Windows 禁止字符替换为全角等价字符,但仍要求用户复核同步预览。 +- 自动检查仍会提醒本地差异;它不会自动触发文件名修复或上传。 +- Windows CI 可验证 TypeScript、测试、构建和打包一致性,不等于 Windows 原生 Obsidian GUI 验收;本版原生 Windows GUI 验收继续延期。 +- 第二设备同步、加密恢复、企业代理和完整附件冲突仍依赖实际环境,不在本记录中声称全部通过。 + +## English + +### Scope + +v0.2.1 changes only what happens **after a preview reports Windows-incompatible paths**: the existing blocking rule and cross-platform policy remain, while an explicit **Repair and preview again** action calls Obsidian's rename API, updates links, writes a rollback log and rescans before preview generation. It does not loosen Git staging rules, rename files in the background or grant upload approval. + +### Checks performed + +- TypeScript: `tsc --noEmit` passed. +- Tests: 24 test files / 103 tests passed; new `path-repair` regression tests cover forbidden characters, trailing punctuation, reserved device names, duplicate targets and existing targets. +- Build: the esbuild production build passed. +- macOS Obsidian 1.15.0: real-host checks covered plugin loading, the repair confirmation dialog, Obsidian renaming, link checks, read-only remote checking and the sync preview; acceptance repaired 1 temporary test file and 4 newly generated real files, verified the rollback log and then removed the temporary file. +- Release archive: the fixed allowlist ZIP, source/build byte equality and standalone bilingual install guides are checked by `scripts/release_archive.py`. + +### Boundaries and limits + +- Repair requires confirmation; the plugin never renames automatically. The conversion uses full-width equivalents for Windows-forbidden characters, and the user must still review the sync preview. +- Automatic checks may continue to report local differences; they never start repair or upload. +- Windows CI can verify TypeScript, tests, builds and archive consistency, but it is not native Windows Obsidian GUI acceptance; that validation remains deferred. +- Second-device sync, encrypted restore, enterprise proxies and full multi-device attachment conflicts remain environment-dependent and are not claimed as fully validated. diff --git a/docs/install/README.md b/docs/install/README.md index eb9137e..8e2a0c8 100644 --- a/docs/install/README.md +++ b/docs/install/README.md @@ -1,4 +1,4 @@ -# GitHub Vault Sync v0.2.0 — Install and upgrade +# GitHub Vault Sync v0.2.1 — Install and upgrade [简体中文](README.zh-CN.md) @@ -22,7 +22,7 @@ This ZIP contains the complete Obsidian desktop plugin and these instructions. * ``` Avoid an extra `github-vault-sync/github-vault-sync/` nesting. The two README files are optional instructions, not runtime dependencies. -3. Open Obsidian, enable **GitHub Vault Sync** in **Settings → Community plugins**, then open its settings and confirm **v0.2.0**. +3. Open Obsidian, enable **GitHub Vault Sync** in **Settings → Community plugins**, then open its settings and confirm **v0.2.1**. 4. Connect with credentials scoped to your private vault repository and review the initial binding plan. Keep PATs and recovery keys out of chats and the public source repository. On Windows the destination is typically `%USERPROFILE%\Documents\YourVault\.obsidian\plugins\github-vault-sync\`; substitute your actual vault path. @@ -32,12 +32,13 @@ On Windows the destination is typically `%USERPROFILE%\Documents\YourVault\.obsi 1. Save notes and exit Obsidian normally; do not use a reload action that discards edits. 2. Back up the current plugin files **and its local `data.json` outside the vault**, with access limited to your OS user account. That settings file may contain a PAT and recovery key. 3. Copy only `manifest.json`, `main.js` and `styles.css` from this package into the existing plugin directory. **Do not delete or replace your existing `data.json`.** Do not replace the entire vault or plugin directory. -4. Reopen Obsidian and verify the running version in plugin settings. v0.2.0 migrates older settings to schema 3, adds AVIF to the Git LFS rules once, and may refresh the local encrypted snapshot. A changed file hash after loading can therefore be legitimate; keep the backup and inspect unexpected changes. +4. Reopen Obsidian and verify the running version in plugin settings. v0.2.1 keeps the schema-3 settings and adds an explicit Windows filename repair flow. Loading the new code does not itself rename notes or grant sync approval. 5. Upgrade-related file or encrypted-bundle changes are not permission to upload. Preview the local and remote differences first, especially when the encrypted bundle has changed on both devices. Do not run competing vault-sync plugins against the same files. ## Preview-first sync and recovery - **Preview and sync** requires an explicit confirmation. Automatic checks notify you of differences; they do not automatically upload the vault. Conflict preferences apply only to Markdown paths changed on both sides; attachments and plugin/configuration files require separate review. +- If a preview reports Windows-incompatible filenames, choose **Repair and preview again** to rename them with Obsidian's API and update note links. Repair is opt-in, refuses colliding targets, writes a rollback log and never uploads or merges by itself. - Media optimization only suggests commands; it never blocks sync or converts files by itself. If you choose to run them, install the documented Pillow/AVIF, Ghostscript and FFmpeg dependencies, keep a cold backup, inspect the outputs and update note links yourself. - On a second device, bind the same private repository and obtain the remote encrypted bundle before creating a new local snapshot. Import your recovery key locally and review the restore action; do not upload an empty bundle over the remote one. - After a remote delete-and-recreate, the other device must first back up its old clone, clone the rebuilt repository again and run `git lfs pull`. Verify the plugin binding and recovery key before resuming two-device sync. @@ -46,12 +47,12 @@ On Windows the destination is typically `%USERPROFILE%\Documents\YourVault\.obsi ## Verified scope and limitations -The v0.2.0 implementation passed local TypeScript checks, 23 test files / 101 tests and a production build. macOS Obsidian 1.14.4 host checks covered the running version, five settings tabs, media preview/copy interactions and read-only remote-rebuild confirmation gating. Windows Git Bash sample conversion commands were exercised separately in CI. The release workflow checks source/build/archive consistency on macOS and Windows. **Windows CI is not native Windows Obsidian GUI or second-device acceptance; native Windows Obsidian GUI validation remains deferred.** Corporate/authenticated proxy networks and full multi-device attachment/restore scenarios remain environment-dependent and are not claimed as fully validated. +The v0.2.1 implementation passed local TypeScript checks, 24 test files / 103 tests and a production build. macOS Obsidian 1.15.0 host checks covered the running version, repair confirmation, Obsidian-based renaming, link checks, read-only remote checking and sync preview. The release workflow checks source/build/archive consistency on macOS and Windows. **Windows CI is not native Windows Obsidian GUI or second-device acceptance; native Windows Obsidian GUI validation remains deferred.** Corporate/authenticated proxy networks and full multi-device attachment/restore scenarios remain environment-dependent and are not claimed as fully validated. The videos and engineering records are **online-only**, deliberately excluded from this installation ZIP: -- [Versioned UI tour and full guide (online)](https://github.com/luoboruodai/github-vault-sync/blob/v0.2.0/README.md) -- [Versioned validation record (online)](https://github.com/luoboruodai/github-vault-sync/blob/v0.2.0/docs/VALIDATION_v0.2.0.md) -- [v0.2.0 Release (online)](https://github.com/luoboruodai/github-vault-sync/releases/tag/v0.2.0) +- [Versioned UI tour and full guide (online)](https://github.com/luoboruodai/github-vault-sync/blob/v0.2.1/README.md) +- [Versioned validation record (online)](https://github.com/luoboruodai/github-vault-sync/blob/v0.2.1/docs/VALIDATION_v0.2.1.md) +- [v0.2.1 Release (online)](https://github.com/luoboruodai/github-vault-sync/releases/tag/v0.2.1) -Download `github-vault-sync-0.2.0.zip` from that Release. GitHub's automatic **Source code** archives omit the built runtime and are not installable plugin packages. +Download `github-vault-sync-0.2.1.zip` from that Release. GitHub's automatic **Source code** archives omit the built runtime and are not installable plugin packages. diff --git a/docs/install/README.zh-CN.md b/docs/install/README.zh-CN.md index 1a23d56..e8b104a 100644 --- a/docs/install/README.zh-CN.md +++ b/docs/install/README.zh-CN.md @@ -1,4 +1,4 @@ -# GitHub Vault Sync v0.2.0 — 安装与升级说明 +# GitHub Vault Sync v0.2.1 — 安装与升级说明 [English](README.md) @@ -22,7 +22,7 @@ ``` 不要多出 `github-vault-sync/github-vault-sync/` 嵌套。两份 README 只是附带说明,不是插件运行依赖。 -3. 打开 Obsidian,在「设置 → 第三方插件」启用 **GitHub Vault Sync**,进入插件设置确认版本为 **v0.2.0**。 +3. 打开 Obsidian,在「设置 → 第三方插件」启用 **GitHub Vault Sync**,进入插件设置确认版本为 **v0.2.1**。 4. 使用仅对目标私有笔记仓库授权的凭据连接,检查首次绑定预览。PAT 和恢复密钥不要贴进聊天或公开源码仓库。 Windows 常见目标路径为 `%USERPROFILE%\Documents\YourVault\.obsidian\plugins\github-vault-sync\`,应以你的真实 Vault 路径为准。 @@ -32,12 +32,13 @@ Windows 常见目标路径为 `%USERPROFILE%\Documents\YourVault\.obsidian\plugi 1. 保存笔记并正常退出 Obsidian;不要使用会丢弃编辑内容的重载入口。 2. 把旧插件安装文件和本机 **`data.json` 一起备份到 Vault 之外**,并限制为当前系统用户可访问。该配置文件可能含 PAT 与恢复密钥。 3. 只将此包中的 `manifest.json`、`main.js`、`styles.css` 覆盖到原插件目录。**保留原有 `data.json`,不要删除或覆盖它**;不要替换整个 Vault 或整个插件目录。 -4. 重开 Obsidian,在插件设置中核对运行版本。v0.2.0 会把旧配置迁移至 schema 3、一次性补充 AVIF 的 Git LFS 规则,并可能更新本地密文镜像;加载后的哈希变化不一定表示配置丢失,应保留备份并检查异常变化。 +4. 重开 Obsidian,在插件设置中核对运行版本。v0.2.1 保持 schema 3 配置,并加入需确认的 Windows 文件名修复流程;仅加载新代码不会自动重命名笔记,也不等于上传授权。 5. 升级产生的安装文件或密文变化**不等于自动上传授权**。先预览本地和远端差异,尤其注意密文包两端都有变化的情况;不要让多个同步插件同时处理同一份 Vault。 ## 安全同步与恢复 - 「预览并同步」必须明确确认。自动检查只发现差异、提醒用户,不会自动上传整库;冲突优先方仅用于两端都改动的同路径 Markdown,附件和插件/配置文件须单独审阅。 +- 若预览提示 Windows 不兼容文件名,可选择「修复并重新预览」,由 Obsidian 重命名 API 修改文件名并更新笔记链接。修复需要用户确认,会拒绝重名目标、保存回滚日志,且不会自行上传或合并。 - 媒体优化只提供命令建议,不阻塞同步,也不会自行转换文件。如选择执行,请先安装说明中的 Pillow/AVIF、Ghostscript 和 FFmpeg 依赖,保留冷备份,检查生成文件并自行更新笔记引用。 - 第二设备先绑定同一私有仓库并取得远端密文包,再在本机导入恢复密钥、检查恢复操作,不要生成空配置包覆盖远端。 - 远程删库同名重建后,另一台设备必须先备份旧克隆,再重新克隆重建后的仓库并执行 `git lfs pull`;核对插件绑定与恢复密钥后才能恢复双端同步。 @@ -46,12 +47,12 @@ Windows 常见目标路径为 `%USERPROFILE%\Documents\YourVault\.obsidian\plugi ## 验证范围与限制 -v0.2.0 已通过本机 TypeScript 检查、23 个测试文件/101 项测试和生产构建。macOS Obsidian 1.14.4 宿主验收覆盖运行版本、五个设置页、媒体预览/复制交互,以及不执行删除的远程重建确认门控;Windows Git Bash 样本转换命令另经 CI 实际执行。发布工作流在 macOS/Windows 检查源码、构建和安装包内容一致性。**Windows CI 不等于 Windows 原生 Obsidian 图形界面或第二设备验收;Windows 原生 Obsidian GUI 验收继续延期。**企业/认证代理以及完整双端附件、恢复场景仍依赖实际环境,不声称已经全部验证。 +v0.2.1 已通过本机 TypeScript 检查、24 个测试文件/103 项测试和生产构建。macOS Obsidian 1.15.0 宿主验收覆盖运行版本、修复确认弹窗、基于 Obsidian 的重命名、链接检查、远端只读检查和同步前确认。发布工作流在 macOS/Windows 检查源码、构建和安装包内容一致性。**Windows CI 不等于 Windows 原生 Obsidian 图形界面或第二设备验收;Windows 原生 Obsidian GUI 验收继续延期。**企业/认证代理以及完整双端附件、恢复场景仍依赖实际环境,不声称已经全部验证。 动效和工程记录仅提供**在线版本**,有意不放进安装 ZIP: -- [当前版本动效与完整指南(需联网)](https://github.com/luoboruodai/github-vault-sync/blob/v0.2.0/README.zh-CN.md) -- [当前版本验证记录(需联网)](https://github.com/luoboruodai/github-vault-sync/blob/v0.2.0/docs/VALIDATION_v0.2.0.md) -- [v0.2.0 Release(需联网)](https://github.com/luoboruodai/github-vault-sync/releases/tag/v0.2.0) +- [当前版本动效与完整指南(需联网)](https://github.com/luoboruodai/github-vault-sync/blob/v0.2.1/README.zh-CN.md) +- [当前版本验证记录(需联网)](https://github.com/luoboruodai/github-vault-sync/blob/v0.2.1/docs/VALIDATION_v0.2.1.md) +- [v0.2.1 Release(需联网)](https://github.com/luoboruodai/github-vault-sync/releases/tag/v0.2.1) -应下载该 Release 的 `github-vault-sync-0.2.0.zip`。GitHub 自动生成的 **Source code** 源码归档不含已构建的运行文件,不能当插件安装包使用。 +应下载该 Release 的 `github-vault-sync-0.2.1.zip`。GitHub 自动生成的 **Source code** 源码归档不含已构建的运行文件,不能当插件安装包使用。 diff --git a/manifest.json b/manifest.json index 7f53673..b61acb0 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "github-vault-sync", "name": "GitHub Vault Sync", - "version": "0.2.0", + "version": "0.2.1", "minAppVersion": "1.13.0", "description": "Safe GitHub and Git LFS synchronization for Obsidian desktop vaults.", "author": "GitHub Vault Sync contributors", diff --git a/package.json b/package.json index 101b1a3..b1b49f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "github-vault-sync", - "version": "0.2.0", + "version": "0.2.1", "description": "Safe GitHub-backed synchronization for Obsidian desktop vaults.", "private": true, "scripts": { diff --git a/releases/github-vault-sync-0.2.1.zip b/releases/github-vault-sync-0.2.1.zip new file mode 100644 index 0000000..e931ae7 Binary files /dev/null and b/releases/github-vault-sync-0.2.1.zip differ diff --git a/src/github.ts b/src/github.ts index b5208ad..8ea0d83 100644 --- a/src/github.ts +++ b/src/github.ts @@ -67,7 +67,7 @@ export class GithubClient { const headers: Record = { Accept: 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28', - 'User-Agent': 'GitHub-Vault-Sync/0.1.3' + 'User-Agent': 'GitHub-Vault-Sync/0.2.1' }; if (options.auth !== false) headers.Authorization = `Bearer ${this.token}`; let body: string | undefined; @@ -112,7 +112,7 @@ export class GithubClient { headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': 'GitHub-Vault-Sync/0.1.3' + 'User-Agent': 'GitHub-Vault-Sync/0.2.1' }, body: new URLSearchParams({ client_id: clientId.trim(), @@ -138,7 +138,7 @@ export class GithubClient { headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': 'GitHub-Vault-Sync/0.1.3' + 'User-Agent': 'GitHub-Vault-Sync/0.2.1' }, body: new URLSearchParams({ client_id: clientId.trim(), device_code: deviceCode.device_code, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' }).toString(), throw: false diff --git a/src/main.ts b/src/main.ts index 3dc5597..fc5c1d8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,6 +22,7 @@ import { backupDataDirectory } from './platform-paths'; import { isMarkdownNote } from './sync-preview'; import { nextSettingsTab, SETTINGS_TABS, SettingsSection, showSettingsTab } from './settings-tabs'; import { normalizeHttpProxy, normalizeSettings } from './settings'; +import { planWindowsPathRepairs, WindowsPathRepair } from './path-repair'; import { ConflictChoice, DEFAULT_EXCLUDE_PATTERNS, @@ -37,7 +38,7 @@ import { SyncSnapshot, VaultScan } from './types'; -import { formatBytes, formatDateTime, isLikelySensitivePath, isSafeRelativePath, matchesAnyPattern } from './utils'; +import { formatBytes, formatDateTime, isLikelySensitivePath, isSafeRelativePath, matchesAnyPattern, scanVault } from './utils'; function vaultBasePath(app: App): string { const adapter = app.vault.adapter as unknown as { getBasePath?: () => string; basePath?: string }; @@ -269,6 +270,65 @@ export default class GithubVaultSyncPlugin extends Plugin { } } + async repairWindowsPaths(paths: readonly string[]): Promise<{ ok: boolean; count: number; log: string; message: string }> { + const plan = planWindowsPathRepairs(paths, (target) => Boolean(this.app.vault.getAbstractFileByPath(target))); + if (plan.errors.length) { + return { ok: false, count: 0, log: '', message: plan.errors.join('\n') }; + } + const directory = path.join(backupDataDirectory(), 'path-repairs'); + await fs.mkdir(directory, { recursive: true, mode: 0o700 }); + if (process.platform !== 'win32') await fs.chmod(directory, 0o700); + const log = path.join(directory, `repair-${new Date().toISOString().replace(/[:.]/g, '-')}.json`); + const completed: WindowsPathRepair[] = []; + const saveLog = () => fs.writeFile(log, JSON.stringify({ repairs: completed }, null, 2), 'utf8'); + try { + for (const repair of plan.repairs) { + const file = this.app.vault.getAbstractFileByPath(repair.from); + if (!(file instanceof TFile)) throw new Error(`Source file is missing: ${repair.from}`); + await this.app.fileManager.renameFile(file, repair.to); + completed.push(repair); + await saveLog(); + } + const after = await scanVault(this.vaultPath, this.settings); + const remaining = after.errors.length + after.caseCollisions.length + after.windowsPathIssues.length; + if (remaining) { + return { ok: false, count: completed.length, log, message: `Rename completed, but ${remaining} unsafe path(s) remain. Review before syncing.` }; + } + return { ok: true, count: completed.length, log, message: `已修复 ${completed.length} 个 Windows 不兼容路径。` }; + } catch (error) { + const failed: WindowsPathRepair[] = []; + for (const repair of [...completed].reverse()) { + try { + const file = this.app.vault.getAbstractFileByPath(repair.to); + if (file && !this.app.vault.getAbstractFileByPath(repair.from)) await this.app.fileManager.renameFile(file, repair.from); + else failed.push(repair); + } catch { + failed.push(repair); + } + } + await saveLog(); + const rollback = failed.length ? `Rollback incomplete for ${failed.length} file(s)` : 'Completed renames were restored'; + return { ok: false, count: 0, log, message: `${error instanceof Error ? error.message : String(error)}; ${rollback}` }; + } + } + + private async offerWindowsPathRepair(error: unknown): Promise { + const message = error instanceof Error ? error.message : String(error); + if (!message.startsWith('Windows-incompatible paths detected:')) return false; + const scan = await scanVault(this.vaultPath, this.settings); + if (!scan.windowsPathIssues.length) return false; + new WindowsPathRepairModal(this.app, scan.windowsPathIssues, async () => { + const result = await this.repairWindowsPaths(scan.windowsPathIssues); + if (!result.ok) { + new Notice(`路径修复未完成:${result.message}${result.log ? `;日志:${result.log}` : ''}`, 12000); + return; + } + new Notice(`${result.message} 正在生成同步预览。恢复日志:${result.log}`, 8000); + await this.syncNow(); + }).open(); + return true; + } + private async startupCheck(): Promise { await this.runAction('startup-check', () => this.engine.checkRemote(), false); const snapshot = this.engine.getSnapshot(); @@ -472,6 +532,7 @@ export default class GithubVaultSyncPlugin extends Plugin { if (result && 'ok' in result && result.ok) this.lastAutoAlert = ''; }, async (file) => this.openNoteComparison(file, preview.remoteSha)).open(); } catch (error) { + if (await this.offerWindowsPathRepair(error)) return; new Notice(`无法生成同步预览:${error instanceof Error ? error.message : String(error)}`, 10000); } finally { this.actionRunning = false; @@ -1315,6 +1376,54 @@ class InitialBindingModal extends GithubVaultSyncModal { onClose(): void { this.contentEl.empty(); } } +class WindowsPathRepairModal extends GithubVaultSyncModal { + private submitting = false; + + constructor(app: App, private readonly issues: readonly string[], private readonly onSubmit: () => Promise) { + super(app); + } + + private async submit(): Promise { + if (this.submitting) return; + this.submitting = true; + try { + await this.onSubmit(); + this.close(); + } finally { + this.submitting = false; + } + } + + onOpen(): void { + const { contentEl } = this; + this.modalEl.addClass('github-vault-sync-note-modal'); + contentEl.empty(); + contentEl.createEl('h2', { text: '修复 Windows 不兼容文件名' }); + contentEl.createEl('p', { + text: `检测到 ${this.issues.length} 个包含 Windows 禁止字符的文件。插件不会自动改名;确认后才会执行。` + }); + contentEl.createEl('p', { + cls: 'setting-item-description', + text: '修复会将 ?、:、"、| 等字符替换为全角等价字符,并通过 Obsidian 更新笔记链接。执行前会检查重名,执行后会保存可回滚日志。' + }); + const list = contentEl.createEl('details', { cls: 'github-vault-sync-initial-list' }); + list.open = this.issues.length <= 20; + list.createEl('summary', { text: `待处理文件(${this.issues.length})` }); + const ul = list.createEl('ul'); + for (const issue of this.issues.slice(0, 30)) ul.createEl('li', { text: issue }); + if (this.issues.length > 30) ul.createEl('li', { text: `另外 ${this.issues.length - 30} 个…` }); + contentEl.createEl('p', { + cls: 'github-vault-sync-hint', + text: '点击“修复并重新预览”只修改本机文件名和链接,不会上传或合并;随后仍会进入同步前确认。' + }); + const actions = new Setting(contentEl).setClass('github-vault-sync-modal-actions'); + actions.addButton((button) => button.setButtonText('取消,不修改').onClick(() => this.close())); + actions.addButton((button) => button.setButtonText('修复并重新预览').setCta().onClick(() => void this.submit())); + } + + onClose(): void { this.contentEl.empty(); } +} + class CurrentNoteModal extends GithubVaultSyncModal { private submitting = false; diff --git a/src/path-repair.ts b/src/path-repair.ts new file mode 100644 index 0000000..996075f --- /dev/null +++ b/src/path-repair.ts @@ -0,0 +1,60 @@ +import { normalizeRelPath } from './utils'; + +export interface WindowsPathRepair { + from: string; + to: string; +} + +export interface WindowsPathRepairPlan { + repairs: WindowsPathRepair[]; + errors: string[]; +} + +const WINDOWS_RESERVED = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i; + +export function convertWindowsPath(path: string): string { + return normalizeRelPath(path).split('/').map((segment) => { + let converted = segment + .replace(//g, '>') + .replace(/:/g, ':') + .replace(/"/g, '"') + .replace(/\|/g, '|') + .replace(/\?/g, '?') + .replace(/\*/g, '*'); + if (converted.endsWith('.')) converted = `${converted.slice(0, -1)}.`; + if (converted.endsWith(' ')) converted = `${converted.slice(0, -1)} `; + if (WINDOWS_RESERVED.test(converted.split('.')[0] ?? '')) converted = `_${converted}`; + return converted; + }).join('/'); +} + +export function planWindowsPathRepairs( + paths: readonly string[], + targetExists: (target: string) => boolean +): WindowsPathRepairPlan { + const repairs: WindowsPathRepair[] = []; + const errors: string[] = []; + const targets = new Set(); + + for (const source of paths) { + const from = normalizeRelPath(source); + const to = convertWindowsPath(from); + if (from === to) { + errors.push(`Path is already compatible: ${from}`); + continue; + } + if (targets.has(to)) { + errors.push(`Multiple files would rename to ${to}`); + continue; + } + if (targetExists(to)) { + errors.push(`Rename target already exists: ${to}`); + continue; + } + targets.add(to); + repairs.push({ from, to }); + } + + return { repairs, errors }; +} diff --git a/tests/path-repair.test.ts b/tests/path-repair.test.ts new file mode 100644 index 0000000..e99146e --- /dev/null +++ b/tests/path-repair.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest'; +import { convertWindowsPath, planWindowsPathRepairs } from '../src/path-repair'; + +describe('Windows path repair', () => { + it('converts reserved characters, trailing punctuation and reserved device names', () => { + expect(convertWindowsPath('笔记/标题? 加:引号" 和|竖线* 。.md')).toBe('笔记/标题? 加:引号" 和|竖线* 。.md'); + expect(convertWindowsPath('folder/file.')).toBe('folder/file.'); + expect(convertWindowsPath('folder/name ')).toBe('folder/name '); + expect(convertWindowsPath('AUX.md')).toBe('_AUX.md'); + }); + + it('plans safe renames and rejects no-ops, duplicate targets and existing targets', () => { + const plan = planWindowsPathRepairs([ + 'a/note?.md', + 'a/note?.md', + 'c/already-safe.md', + 'd/existing?.md' + ], (target) => target === 'd/existing?.md'); + + expect(plan.repairs).toEqual([{ from: 'a/note?.md', to: 'a/note?.md' }]); + expect(plan.errors).toEqual([ + 'Multiple files would rename to a/note?.md', + 'Path is already compatible: c/already-safe.md', + 'Rename target already exists: d/existing?.md' + ]); + }); +}); diff --git a/versions.json b/versions.json index b100847..1f01fe0 100644 --- a/versions.json +++ b/versions.json @@ -3,5 +3,6 @@ "0.1.1": "1.13.0", "0.1.2": "1.13.0", "0.1.3": "1.13.0", - "0.2.0": "1.13.0" + "0.2.0": "1.13.0", + "0.2.1": "1.13.0" }