Typora plugin. Feature enhancement tool | Typora 插件,功能增强工具
# AGENTS.md
This file provides guidance to AI coding assistants and agents when working with code in this repository.
## Project Overview
Typora Plugin is an extensible plugin system for the Typora Markdown editor. It injects into Typora's Electron-based runtime (via `window.html`) and provides 50+ plugins. The project is pure JavaScript (no TypeScript), requires Typora >= 0.9.98, and supports Windows and Linux.
**Compatibility Target:**
Because this project must support Typora 0.9.98, any code injected into the Typora runtime must be compatible with its underlying legacy Electron version. The minimum supported environment limits are:
- **Chrome**: 84 (do not rely on features introduced after Chrome 84)
- **Node.js**: 12.14.1
## Project Constraints
### Storage and Concurrency
- For small amounts of plugin data, use `utils.getStorage` instead of writing files under `user_space`. The latter is prone to permission errors.
- Keep this storage flow simple: do not add file locking when using `utils.getStorage` for these small data sets.
### Legacy Runtime Compatibility
- Code running inside Typora must remain compatible with Typora 0.9.98 and Chrome 84. Avoid JavaScript, DOM, and CSS features introduced after that browser version.
- In particular, do not use Flexbox `gap`. Use compatible DOM operations and spacing techniques instead.
- If a newer JavaScript or DOM API is truly needed, add an appropriate compatibility implementation to `plugin/global/core/polyfill.js`. CSS features still require a compatible CSS fallback.
### Theme-safe Plugin UI
- Typora Plugin is not an official Typora project, so user themes may contain broad selectors that affect plugin UI. For plugin-owned UI markup, prefer `div` elements and avoid other HTML tags whenever practical.
- Do not introduce native `select`, `option`, or `button` elements in plugin UI. Use the existing `fast-dropdown` component instead of `select`/`option`, and use a regular `div` for button-like controls.
- Refer to the `commander` plugin for established `fast-dropdown` usage. The shared `fast-dropdown` component may be adjusted when the required behavior cannot be expressed by its current API.
- Plugin dialogs should first consider reusing the shared `fast-window` component, following examples such as `search_multi` and `commander`. Reuse is preferred for dialogs such as `.repository-dialog`, but is not mandatory when the component is unsuitable.
### Text Input Handling
- For search and similar live text inputs, use `utils.createSmartInputHandler` so IME composition (including Chinese input) is handled correctly. Do not implement these handlers as `addEventListener("input", utils.debounce(...))`.
## Commands
All commands run from the `develop/` directory. Requires Node.js >= 22.
```bash
cd develop
npm install # Install dev dependencies
# Testing (uses Node.js built-in test runner: node:test + node:assert)
npm test # Run all tests
node --require ../plugin/global/core/polyfill.js --test test/utils.test.js # Run single test file
# Building vendored dependencies (esbuild bundles NPM packages into plugin/global/core/lib/)
npm run build:all # Build all vendors
npm run build:single # Build a single vendor (edit arg in package.json, e.g. "katex")
npm run build:download # Build download-type vendors (js-yaml, markdown-it, etc.)
# Development (requires TYPORA_PATH set in develop/.env)
npm run dev # Development mode
npm run sync # Watch plugin/ for changes, sync to Typora install dir
npm run serve # Sync + auto-restart Typora via JSON-RPC
npm run rpc # JSON-RPC connection to running Typora
```
## Architecture
### Entry Point Flow
1. `plugin/index.js` -- loaded by modified `window.html`, requires `plugin/global/core/index.js`
2. `plugin/global/core/index.js` -- `entry()` function: checks Typora version, reads TOML settings, sets up globals, initializes i18n, loads all plugins via mixin chain, publishes `allPluginsHadInjected` event
### Core Framework (`plugin/global/core/`)
- **`plugin.js`** -- `BasePlugin` classes, and `LoadPlugins()` which drives the plugin lifecycle
- **`serviceContainer.js`** -- Singleton storing all plugin instances and settings; provides lookup APIs (`getPlugin()`)
- **`i18n.js`** -- i18n system supporting `en`, `zh-CN`, `zh-TW`; loads JSON locale files from `plugin/global/locales/`
- **`polyfill.js`** -- Polyfills for older Electron/Node (`Object.hasOwn`, `Promise.withResolvers`, etc.)
### Core Utilities (`plugin/global/core/utils/`)
- **`index.js`** -- Large utility class (~200+ static methods): DOM manipulation, file ops, path handling, HTML/CSS injection. Instantiates all mixins.
- **`eventHub.js`** -- Event bus with typed events (`fileOpened`, `fileEdited`, `outlineUpdated`, etc.). Uses MutationObserver and decorator hooks.
- **`hotkeyHub.js`** -- Global hotkey registration/dispatch. Normalizes key combos (Ctrl+Shift+Alt+Key).
- **`decorator.js`** -- AOP system. Wraps Typora's internal functions with before/after hooks, argument/result modification, call prevention. Supports decorator chaining with priorities.
- **`settings.js`** -- TOML settings reader (default + user), supports save/import/export, auto-save via Proxy.
- **`styleManager.js`** -- CSS loading with template variable substitution (`${config.value}`).
- **`thirdPartyDiagramParser.js`** -- Extended diagram framework with lazy-loading and export support.
### Plugin System
**Plugin Lifecycle** (in order): `prepare()` -> `style()` -> `html()` -> `hotkey()` -> `init()` -> `process()` -> `postprocess()`
### Settings System (`plugin/global/settings/`)
- `settings.default.toml` (~120KB) -- Default config for all base plugins. Each plugin has `[plugin_name]` section with `ENABLE`, `NAME`, and plugin-specific options.
- `settings.user.toml` -- User overrides
- Supports home directory override (`~/.config/typora_plugin/`) for persistence across updates
### Key Patterns
- **Service Container / DI**: `ServiceContainer` singleton holds all plugin instances, accessible via `utils.container`
- **AOP (Aspect-Oriented Programming)**: `decorator.js` wraps Typora internals without modifying source. Used by `eventHub`, `exportHelper`, and many plugins.
- **Mixin Architecture**: Core features (eventHub, hotkeyHub, styleManager, etc.) are mixins on the `utils` class, each with `process()` and optional `postprocess()` lifecycle methods
- **Vendored Dependencies**: All NPM dependencies are pre-bundled via esbuild into `plugin/global/core/lib/` -- no runtime `npm install` needed in `plugin/`
- **Event-Driven**: Rich event system for file operations, code block changes, sidebar toggling, etc.
## Code Style
- Pure JavaScript, no TypeScript
- UTF-8, 2-space indent, LF line endings (see `.editorconfig`)
- All UI strings go through the i18n system (locale files in `plugin/global/locales/`)
- When adding a new plugin: add a `[plugin_name]` section to `settings.default.toml` with at least `ENABLE` and `NAME` keys, add translations to all three locale JSON files, and optionally add a CSS file to `plugin/global/styles/`
## Debugging
- **Open DevTools**: To open Typora's Developer Tools for debugging or inspecting the DOM and console logs, use the following JS command:
```javascript
JSBridge.invoke("window.toggleDevTools")
```
## Testing
- Framework: Node.js built-in `node:test` + `node:assert`
- Test files are in `develop/test/`
- Tests use JSDOM for DOM mocking (`develop/test/mocks/dom.mock.js`), proxyquire for module mocking (`utils.mock.js`), and fixture files for integration-style tests
- The polyfill (`plugin/global/core/polyfill.js`) must be loaded via `--require` before tests
## Build System
The build (`develop/build/index.cjs`) uses esbuild to bundle NPM dependencies into standalone files under `plugin/global/core/lib/` and individual plugin directories. Three vendor types:
- **download**: Fetch minified files from CDN/GitHub
- **bundle**: esbuild bundles NPM packages (options: `{ bundle: true, minify: true, platform: "node", target: "node12.14" }`)
- **dist**: Copy NPM package assets directly (e.g., katex fonts + CSS)
## CI/CD
- `TestOnCommit.yaml` -- Runs `npm ci && npm test` on push to `develop/**`, `plugin/**`, `.github/**` (Node 20.x and 24.x matrix)
- `PublishOnTag.yaml` -- On tag `X.Y.Z`, creates VERSION.json, zips `plugin/`, publishes GitHub Release