diff --git a/README.md b/README.md index 09ac219..00f324d 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,224 @@ # Anki Heading Sync -Anki Heading Sync is an Obsidian desktop plugin for syncing Markdown heading blocks into Anki with a focused, file-first workflow. +Anki Heading Sync is an Obsidian desktop plugin that syncs Markdown heading blocks into Anki through a file-first workflow. -## Features +It is designed for users who want to keep note authoring in Obsidian, push selected heading blocks to Anki, and let the plugin write stable sync markers back into the same Markdown files. -- Sync basic Q&A cards from heading blocks -- Sync cloze cards from heading blocks with cloze-specific recognition -- Sync QA Group list blocks into multi-level list QA notes -- Sync the current file or all in-scope files in the vault -- Write Anki IDs and GI markers back into Markdown after sync -- Support deck routing, Obsidian backlinks, tag sync, and current-file reset +## What the plugin supports + +- Basic Q&A cards from heading blocks +- Cloze cards with the sequential marker route `#anki-cloze` +- Cloze-all cards with the all-in-one marker route `#anki-cloze-all` +- QA Group list notes with the group marker route `#anki-list` +- Syncing either the current file or all files inside the configured vault scope +- Deck routing through default deck, folder mapping, and optional file-level custom deck +- Optional Obsidian backlinks and tag sync +- Clearing synced markers in the current file and cleaning up empty decks in Anki ## Requirements - Obsidian desktop 1.5.0 or newer -- Anki with AnkiConnect enabled +- Anki desktop +- AnkiConnect enabled in Anki + +This plugin is desktop-only. It talks to the configured AnkiConnect URL, which defaults to `http://127.0.0.1:8765`. + +The plugin does not upload your notes to a remote service by itself. Network requests are sent only to the AnkiConnect URL you configure in the plugin settings. + +## Install + +### Community Plugins + +Once the plugin is accepted into the Obsidian Community Plugins directory, install it from Obsidian's Community Plugins browser. + +### Manual install from a release + +1. Download the release assets for the version you want. +2. Create or open your vault plugin folder: `.obsidian/plugins/anki-heading-sync`. +3. Copy these release assets into that folder: + - `main.js` + - `manifest.json` + - `styles.css` when present +4. Enable Anki Heading Sync in Obsidian. + +## AnkiConnect setup + +1. Open Anki on your desktop. +2. Install and enable the AnkiConnect add-on in Anki. +3. Restart Anki after installing the add-on. +4. In Obsidian, open Settings -> Community plugins -> Anki Heading Sync. +5. Keep the default AnkiConnect URL `http://127.0.0.1:8765` unless you intentionally expose AnkiConnect on another address. +6. Confirm that Anki is running before you start a sync. + +## Initial plugin setup + +### 1. Choose note types and field mappings + +Open the plugin settings and configure the note types and field mappings used by the routes you want to sync. + +- Basic cards use the basic route. +- Cloze and cloze-all use the cloze routes. +- QA Group uses the dedicated group route. + +If you change note model fields in Anki later, revisit the plugin settings and refresh the mappings before syncing again. + +### 2. Configure sync scope + +Vault sync uses the configured run scope. + +- `all`: sync every Markdown file the plugin can index +- `include`: sync only the folders listed in `includeFolders` +- `exclude`: sync everything except the folders listed in `excludeFolders` + +If `include` mode is selected and the include list is empty, vault sync has no in-scope files to process. + +### 3. Configure deck behavior + +Set a default deck first. You can then optionally add folder-based deck routing and file-level custom deck declarations. + +Current deck priority is: + +1. File-level custom deck +2. Folder mapping deck +3. Default deck ## Commands -- Sync current file to Anki -- Sync vault to Anki -- Clear synced cards in current file -- Clean up empty decks +- `Sync current file to Anki` +- `Sync vault to Anki` +- `Clear synced cards in current file` +- `Clean up empty decks` + +The plugin registers these commands without default hotkeys so they do not override user shortcuts. + +## Supported Markdown patterns + +### Basic Q&A + +By default, a plain H4 heading block is treated as a basic card. + +```md +#### Capital of France +Paris +``` + +### Cloze, sequential route + +Use the cloze marker on the heading. Native Anki cloze syntax is preserved. + +```md +#### Atomic structure #anki-cloze +Electrons orbit the {{c1::nucleus}}. +``` + +### Cloze, all route + +Use the cloze-all marker on the heading when all generated clozes in the block should share the same deletion index. + +```md +#### Key ideas #anki-cloze-all +{Alpha} and {Beta} are reviewed together. +``` + +### QA Group list note + +Use the QA Group marker on the heading. The whole heading block syncs as one QA Group note. + +```md +#### Concepts #anki-list +- Alpha + - First answer +- Beta + - Second answer +``` + +### Multi-level list QA example + +Nested content under the answer bullet stays inside the answer region. + +```md +#### Physics laws #anki-list +- Newton's first law + - An object remains at rest or in uniform motion. + - Unless a net external force acts on it. +- Newton's second law + - Force equals mass times acceleration. +``` + +## Marker write-back + +After a successful sync, the plugin writes sync markers back into the Markdown file. + +- Single-card routes write an Anki note marker such as `` +- QA Group routes write a group marker such as `` + +The plugin writes these markers into the Markdown file so later syncs can update the existing Anki notes instead of creating duplicates. + +The `Clear synced cards in current file` command removes synced marker state from the current file so you can intentionally recreate notes. + +## Answer cutoff behavior + +The plugin supports two answer cutoff modes. + +- `heading-block`: keep the entire heading block as card content unless a valid sync marker is present +- `double-blank-lines`: stop before the first run of 2 or more blank lines unless a valid sync marker is present + +If a valid `` or `` marker already exists, that marker takes precedence over the blank-line cutoff. + +## Privacy and local-network behavior + +- The plugin sends requests only to the configured AnkiConnect URL. +- The default URL is local: `http://127.0.0.1:8765`. +- The plugin modifies Markdown files in your vault to write sync markers and maintain note identity. +- Plugin settings and state are stored through Obsidian plugin data storage, which results in plugin data being persisted in the plugin `data.json` file. + +## Troubleshooting + +### Anki is not open + +Open Anki first, then run the sync command again. The plugin cannot sync while AnkiConnect is unavailable. + +### AnkiConnect is not installed or not responding + +Confirm that AnkiConnect is installed in Anki, restart Anki, and verify the plugin's AnkiConnect URL setting. + +### Field mapping is stale + +If you changed note fields or note types in Anki, revisit the plugin settings and remap the fields before syncing again. + +### Deck routing is not what you expected + +Check the deck priority order: file-level custom deck overrides folder mapping, and folder mapping overrides the default deck. + +### Vault sync does not pick up any files + +Check the sync scope settings. In `include` mode, an empty include list means there are no in-scope files. + +### You want to recreate old notes instead of updating them + +Use `Clear synced cards in current file` to remove the current file's sync markers, then sync again. ## Development ```bash npm install npm run lint -npm run test +npm test npm run build ``` -Release assets should include `main.js`, `manifest.json`, and `styles.css`. +To build and sync the bundled plugin into a local vault during development, set `OBSIDIAN_PLUGIN_DIR` if you want to override the default local target path: + +```bash +OBSIDIAN_PLUGIN_DIR="/absolute/path/to/.obsidian/plugins/Anki Heading Sync" npm run build:obsidian +``` + +The vault sync step copies only the built plugin assets and does not overwrite `data.json`. + +## Release assets + +GitHub release assets for Obsidian should include: + +- `main.js` +- `manifest.json` +- `styles.css` when present diff --git a/docs/obsidian-community-release-checklist.md b/docs/obsidian-community-release-checklist.md new file mode 100644 index 0000000..f0a0528 --- /dev/null +++ b/docs/obsidian-community-release-checklist.md @@ -0,0 +1,61 @@ +# Obsidian Community Release Checklist + +## Final local validation + +- Confirm manifest.json `id`, `name`, `version`, `description`, `author`, `authorUrl`, `minAppVersion`, and `isDesktopOnly` are correct. +- Confirm versions.json maps the release version to the correct minAppVersion. +- Confirm README.md and LICENSE are present in the repository root. +- Run `npm run lint`. +- Run `npm test`. +- Run `npm run build`. +- Verify dist/plugin contains `main.js`, `manifest.json`, and `styles.css` when present. + +## Version and release + +- If needed, bump manifest.json `version` in `x.y.z` format. +- Update versions.json with the same version key and the correct minAppVersion value. +- Create a GitHub release tag that exactly matches manifest.json `version`. +- Upload these release assets: + - main.js + - manifest.json + - styles.css +- Download the uploaded assets once to verify the release is usable. + +## Local vault sync for final smoke test + +- Optionally run `npm run build:obsidian` for a local smoke test. +- If you need a custom vault target, set `OBSIDIAN_PLUGIN_DIR` before running the command. +- Confirm the sync step copied only plugin assets and did not overwrite data.json. + +## Manual QA before submission + +- Enable the plugin in a desktop Obsidian vault. +- Open the plugin settings page. +- Confirm AnkiConnect works at the configured URL. +- Verify basic Q&A sync. +- Verify `#anki-cloze` sync. +- Verify `#anki-cloze-all` sync. +- Verify `#anki-list` QA Group sync. +- Verify marker write-back for `` and ``. +- Verify scope handling for current-file sync and vault sync. +- Verify deck behavior with default deck and any overrides you use. +- Verify `Clear synced cards in current file`. +- Verify `Clean up empty decks`. +- Verify error messaging when Anki is closed. + +## Obsidian Releases PR + +- Fork obsidianmd/obsidian-releases. +- Append the plugin entry to community-plugins.json. +- Use the exact repository identifier `panAtGitHub/AnkiHeadingSync`. +- Open a PR titled `Add plugin: Anki Heading Sync`. +- In preview mode, choose the Community Plugin checklist. +- Fill in the PR template and reviewer notes. + +## Reviewer notes to include + +- The plugin is desktop-only. +- The plugin depends on AnkiConnect. +- Network requests go only to the configured AnkiConnect URL, default `http://127.0.0.1:8765`. +- The plugin writes sync markers back into Markdown files. +- Plugin settings and sync state are stored in the plugin data.json file. diff --git a/docs/obsidian-community-release-readiness-report.md b/docs/obsidian-community-release-readiness-report.md new file mode 100644 index 0000000..83fbef0 --- /dev/null +++ b/docs/obsidian-community-release-readiness-report.md @@ -0,0 +1,184 @@ +# Obsidian Community Release Readiness Report + +Date: 2026-04-29 + +This report is based on the current repository state plus the current Obsidian release documentation: + +- Submit your plugin +- Submission requirements for plugins +- Plugin guidelines +- Developer policies + +Repository evidence was verified against these files and modules: + +- manifest.json +- versions.json +- package.json +- package-lock.json +- README.md +- LICENSE +- main.js +- styles.css +- esbuild.config.mjs +- scripts/stage-plugin-dist.mjs +- scripts/sync-plugin-dist.mjs +- scripts/obsidian-plugin-path.mjs +- src/presentation/commands/registerCommands.ts +- src/application/config/PluginSettings.ts +- src/infrastructure/anki/AnkiConnectGateway.ts +- src/infrastructure/obsidian/ObsidianVaultGateway.ts +- src/infrastructure/obsidian/ObsidianPluginDataStore.ts + +## 1. Current repository status + +- The GitHub repository page at `panAtGitHub/AnkiHeadingSync` is publicly reachable. +- The GitHub repository currently shows no published releases, so the first submission release still needs to be created. +- No exact `anki-heading-sync` ID match and no exact `Anki Heading Sync` name match were found in the current upstream Obsidian community-plugins.json. +- manifest.json contains the expected release-facing fields: `id`, `name`, `version`, `minAppVersion`, `description`, `author`, `authorUrl`, and `isDesktopOnly`. +- versions.json currently maps `1.0.0` to `1.5.0`, which matches manifest.json version and minAppVersion. +- package.json defines the release-relevant commands: `lint`, `test`, `build`, and `build:obsidian`. +- package-lock.json is present, and no yarn.lock was found. +- LICENSE is present and uses MIT. +- README.md is present and now documents purpose, setup, usage, disclosures, and troubleshooting. +- styles.css exists. It is intentionally empty, which is acceptable because the release pipeline still stages the file when present. +- esbuild.config.mjs builds the bundled plugin entry to `dist/plugin/main.js` and copies the production bundle back to the repository root as main.js. +- scripts/stage-plugin-dist.mjs stages manifest.json, versions.json, and styles.css into dist/plugin before bundling. +- scripts/sync-plugin-dist.mjs copies only `main.js`, `manifest.json`, and `styles.css` from dist/plugin into the local vault plugin directory. +- scripts/obsidian-plugin-path.mjs now supports `OBSIDIAN_PLUGIN_DIR` and otherwise falls back to a local default under the current user's home directory instead of a hardcoded absolute username path. +- src/presentation/commands/registerCommands.ts registers four commands and does not assign default hotkeys. +- src/application/config/PluginSettings.ts sets the default AnkiConnect URL to `http://127.0.0.1:8765`, enables include-scope by default, and declares the default card routes for basic, cloze, cloze-all, and QA Group. +- src/infrastructure/anki/AnkiConnectGateway.ts sends requests through Obsidian's requestUrl to the configured AnkiConnect URL. +- src/infrastructure/obsidian/ObsidianVaultGateway.ts uses `vault.process()` for Markdown write-back. +- src/infrastructure/obsidian/ObsidianPluginDataStore.ts uses `loadData()` and `saveData()` for plugin persistence. +- Local release validation on this branch is currently green: `npm run lint` passed, `npm test` passed with 312 tests across 35 files, and `npm run build` passed. +- After the build, dist/plugin contains `README.md`, `main.js`, `manifest.json`, `styles.css`, and `versions.json`. + +## 2. Required Obsidian submission checklist + +Based on the current Obsidian docs, the repository must satisfy these release conditions: + +- Keep README.md, LICENSE, and manifest.json in the repository root. +- Use a manifest version in `x.y.z` format. +- Create a GitHub release whose tag exactly matches manifest.json `version`. +- Upload `main.js`, `manifest.json`, and optional `styles.css` as release assets. +- Keep manifest metadata aligned with the community-plugins.json submission entry. +- Keep description short, simple, and ending with a period. +- Ensure command IDs do not repeat the plugin ID prefix. +- Avoid default hotkeys. +- Use `isDesktopOnly: true` when the plugin is intentionally desktop-only. +- Clearly disclose network use in README. +- Clearly disclose Markdown write-back and any plugin data storage behavior in README. + +## 3. What is already ready + +- The GitHub repository is publicly reachable, which satisfies the source-availability expectation for review. +- The plugin ID and plugin name do not currently collide with an upstream community-plugins entry. +- Required root files exist: README.md, LICENSE, manifest.json, main.js, styles.css, versions.json. +- manifest.json metadata is internally consistent with versions.json. +- The manifest description is short, simple, and ends with a period. +- The plugin ID is `anki-heading-sync`, which does not contain `obsidian`. +- The command IDs in src/presentation/commands/registerCommands.ts do not duplicate the plugin ID. +- No default hotkeys are registered. +- Markdown file changes use `vault.process()` rather than a less safe background modify path. +- Plugin data persistence uses Obsidian plugin storage APIs. +- The build pipeline already produces the expected release files under dist/plugin. +- The vault sync script copies only the built plugin assets and does not target data.json. +- README now discloses local-network use, Markdown write-back, and plugin data persistence. + +## 4. What still needs fixing + +- A GitHub release for version `1.0.0` still needs to be created manually, with assets attached. +- Manual QA in real Obsidian and real Anki is still required before submission. +- The actual submission PR to obsidianmd/obsidian-releases still needs to be opened. + +## 5. High-risk blockers + +- If the GitHub repository is not public and reachable, the plugin cannot be reviewed. +- If the GitHub release tag does not exactly match `manifest.json.version`, Obsidian will not install the published release correctly. +- If live manual QA finds problems in AnkiConnect connectivity, marker write-back, or deck routing, submission should pause until they are fixed. + +At the repository level, no code-path blocker was found in the files reviewed above. + +## 6. Medium-risk improvements + +- Root-level agent and internal workflow docs still mention a local vault path in non-runtime documentation. They are not used by the release scripts, but they remain visible in the public repository and may be worth pruning later. +- A short PR note explaining why the plugin is desktop-only would reduce reviewer back-and-forth, because the runtime path is centered on desktop Anki plus AnkiConnect. +- A short release-note template would make later version bumps less error-prone. + +## 7. Low-risk polish + +- Add screenshots or a short demo GIF later if you want a stronger first impression on GitHub. This is not required for Obsidian submission. +- Add a short CHANGELOG entry for the first public release if you want cleaner release notes. This is optional. + +## 8. Manual QA checklist + +Run these checks in a real desktop Obsidian vault with real Anki running: + +- Enable the plugin in a clean vault. +- Open the settings tab once after first install. +- Confirm the default AnkiConnect URL is `http://127.0.0.1:8765`. +- Configure note types and field mappings. +- Run `Sync current file to Anki` on a file with a basic card. +- Run `Sync current file to Anki` on a file with `#anki-cloze` and `#anki-cloze-all` blocks. +- Run `Sync current file to Anki` on a file with a `#anki-list` QA Group block. +- Confirm `` and `` markers are written back as expected. +- Run `Sync vault to Anki` and confirm include or exclude scope behaves as configured. +- Verify deck routing with default deck, folder mapping, and file-level custom deck if you use them. +- Run `Clear synced cards in current file` and verify the file marker state is removed as expected. +- Run `Clean up empty decks` and confirm only still-empty decks are removed. +- Turn off or close Anki and confirm the user-facing error is understandable. +- Sanity-check both English and Chinese UI text if you ship both locales. + +## 9. GitHub Release checklist + +- Confirm manifest.json `version` is the release version you want to ship. +- Confirm versions.json contains the same version mapped to the correct minAppVersion. +- Run `npm run lint`. +- Run `npm test`. +- Run `npm run build`. +- Verify dist/plugin contains `main.js`, `manifest.json`, and `styles.css` when present. +- Create a GitHub release tag exactly matching `manifest.json.version`, currently `1.0.0`. +- Upload these assets to the release: + - main.js + - manifest.json + - styles.css +- Download at least one release asset once to confirm the release is complete. + +## 10. Obsidian Releases PR checklist + +- Fork obsidianmd/obsidian-releases. +- Append a new entry to community-plugins.json using the exact manifest metadata. +- Use the current repo identifier `panAtGitHub/AnkiHeadingSync`. +- Open the PR in preview mode and select the Community Plugin checklist. +- Use the PR title `Add plugin: Anki Heading Sync`. +- Fill in the template checkboxes and reviewer notes before submitting. +- Wait for the automation result and only proceed when the PR shows a ready-for-review state. + +## 11. Security/privacy disclosure checklist + +README.md should continue to state all of the following clearly: + +- The plugin talks to AnkiConnect through the configured URL. +- The default URL is local: `http://127.0.0.1:8765`. +- The plugin does not upload notes to a remote service by itself. +- The plugin modifies Markdown files to write sync markers and maintain note identity. +- Plugin settings and state are persisted through Obsidian plugin storage, which results in data being stored in the plugin data.json file. + +Reviewer notes should also call out: + +- desktop-only positioning +- AnkiConnect dependency +- local network request behavior +- Markdown marker write-back +- plugin data storage in data.json + +## 12. Final go/no-go status + +Current status: almost ready. + +Repository-level release preparation is close to complete, but submission should wait until all of the following are true: + +- local lint, test, and build validation remain green on the release branch +- manual QA is completed in real Obsidian and real Anki +- the GitHub release for version `1.0.0` exists with the correct assets +- the submission PR to obsidianmd/obsidian-releases is opened with the correct metadata diff --git a/docs/obsidian-releases-pr-entry-draft.json b/docs/obsidian-releases-pr-entry-draft.json new file mode 100644 index 0000000..4b8c2c9 --- /dev/null +++ b/docs/obsidian-releases-pr-entry-draft.json @@ -0,0 +1,7 @@ +{ + "id": "anki-heading-sync", + "name": "Anki Heading Sync", + "author": "Dusk", + "description": "Focused heading-based Obsidian to Anki sync plugin.", + "repo": "panAtGitHub/AnkiHeadingSync" +} diff --git a/scripts/obsidian-plugin-path.mjs b/scripts/obsidian-plugin-path.mjs index c72925d..aa85e81 100644 --- a/scripts/obsidian-plugin-path.mjs +++ b/scripts/obsidian-plugin-path.mjs @@ -1,2 +1,16 @@ -export const OBSIDIAN_PLUGIN_DIR = - "/Users/panxiaorong/Library/Mobile Documents/iCloud~md~obsidian/Documents/obsidian/.obsidian/plugins/Anki Heading Sync"; +import { homedir } from "node:os"; +import { join } from "node:path"; + +const DEFAULT_PLUGIN_DIR = join( + homedir(), + "Library", + "Mobile Documents", + "iCloud~md~obsidian", + "Documents", + "obsidian", + ".obsidian", + "plugins", + "Anki Heading Sync", +); + +export const OBSIDIAN_PLUGIN_DIR = process.env.OBSIDIAN_PLUGIN_DIR?.trim() || DEFAULT_PLUGIN_DIR;