This file provides guidance to Qoder (lingma.aliyun.com) when working with code in this repository.
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.
All commands run from the develop/ directory. Requires Node.js >= 22.
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 Typoraplugin/index.js-- loaded by modifiedwindow.html, requiresplugin/global/core/index.jsplugin/global/core/index.js--entry()function: checks Typora version, reads TOML settings, sets up globals (BasePlugin,BaseCustomPlugin), initializes i18n, loads all plugins via mixin chain, publishesallPluginsHadInjectedevent
plugin.js-- DefinesIPlugin(base interface),BasePlugin,BaseCustomPluginclasses, andLoadPlugins()which drives the plugin lifecycleserviceContainer.js-- Singleton storing all plugin instances and settings; provides lookup APIs (getBasePlugin(),tryGetPlugin())i18n.js-- i18n system supportingen,zh-CN,zh-TW; loads JSON locale files fromplugin/global/locales/polyfill.js-- Polyfills for older Electron/Node (Object.hasOwn,Promise.withResolvers, etc.)
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 Lifecycle (in order): prepare() -> style() -> html() -> hotkey() -> init() -> process() -> finalize()
Two plugin types:
- Base Plugins (in
plugin/) -- ExtendBasePlugin, implementcall(action, meta). Can be single-file (plugin/{name}.js) or directory-based (plugin/{name}/index.jswith resources). - Custom Plugins (in
plugin/custom/plugins/) -- ExtendBaseCustomPlugin, implementselector(),hint(),callback(). Managed by thecustombase plugin (plugin/custom/index.js). User-populated, empty by default.
settings.default.toml(~120KB) -- Default config for all base plugins. Each plugin has[plugin_name]section withENABLE,NAME, and plugin-specific options.settings.user.toml-- User overridescustom_plugin.default.toml/custom_plugin.user.toml-- Same pattern for custom plugins- Supports home directory override (
~/.config/typora_plugin/) for persistence across updates
- Service Container / DI:
ServiceContainersingleton holds all plugin instances, accessible viautils.container - AOP (Aspect-Oriented Programming):
decorator.jswraps Typora internals without modifying source. Used byeventHub,exportHelper, and many plugins. - Mixin Architecture: Core features (eventHub, hotkeyHub, styleManager, etc.) are mixins on the
utilsclass, each withprocess()and optionalpostprocess()lifecycle methods - Vendored Dependencies: All NPM dependencies are pre-bundled via esbuild into
plugin/global/core/lib/-- no runtimenpm installneeded inplugin/ - Event-Driven: Rich event system for file operations, code block changes, sidebar toggling, etc.
- 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 tosettings.default.tomlwith at leastENABLEandNAMEkeys, add translations to all three locale JSON files, and optionally add a CSS file toplugin/global/styles/
- 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--requirebefore tests
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)
TestOnCommit.yaml-- Runsnpm ci && npm teston push todevelop/**,plugin/**,.github/**(Node 20.x and 24.x matrix)PublishOnTag.yaml-- On tagX.Y.Z, creates version.json, zipsplugin/, publishes GitHub Release