README-en.md
May 17, 2026 · View on GitHub
custom:Open Capabilities
The custom plugin offers open capabilities, allowing you to create and integrate custom functionalities within the right-click menu.
Core Features
The custom plugin enables customization through declarative configuration and code:
- Register UI elements and behaviors: Supports defining CSS styles (
style), HTML elements (html), right-click menu hints (hint), and keyboard shortcuts (hotkey). - Flexible Lifecycle: Provides lifecycle functions such as
prepare,init,process,finalize, andcallback, allowing you to execute custom logic at different stages of the plugin. - Cursor Position Control: Use
selectorto define the cursor positions where the plugin is available.
How to Use
Customizing a plugin involves just two steps:
- Add Plugin Configuration: Add the plugin configuration to the
./plugin/global/settings/custom_plugin.user.tomlfile. - Write Plugin Code: Create a
.jsfile with the same name as the plugin in the./plugin/custom/pluginsdirectory, write a class that inherits fromBaseCustomPlugin, and export it asplugin.
Examples
Quick Start
This example demonstrates how to create a simple plugin that displays a pop-up message and outputs information to the console when triggered by a right-click menu selection or a keyboard shortcut.
Step 1: Add the following configuration to ./plugin/global/settings/custom_plugin.user.toml:
[helloWorld]
name = "hellworld" # Display name of the plugin in the right-click menu
enable = true # Controls whether the plugin is enabled
hide = false # Controls whether the plugin is hidden in the right-click menu
order = 1 # Display order of the plugin in the right-click menu (larger value means later, supports negative numbers)
hotkey = "ctrl+alt+u" # Defines the shortcut to trigger the plugin
console_message = "I am in process" # Defines the message to be output to the console
show_message = "this is hello world plugin" # Defines the message to be displayed in the pop-up
name,enable,hide, andorderare mandatory. The rest are personalized configurations for the plugin.
Step 2: Create the file ./plugin/custom/plugins/helloWorld.js and save the following code in it:
// ./plugin/custom/plugins/helloWorld.js
class helloWorld extends BaseCustomPlugin {
// executed first to check preconditions for plugin execution
// If conditions are not met, it should return this.utils.PLUGIN_LOAD_ABORT to stop plugin loading
prepare = async () => {
// In actual development, please replace with meaningful condition check logic
if (false) {
return this.utils.PLUGIN_LOAD_ABORT
}
}
// Registers CSS styles. Returns a string that will be automatically inserted into the DOM as a <style> tag
style = () => "#hello-world { margin: 10px; }"
// Registers DOM elements. Can return an Element type or a string representing the element, which will be automatically inserted into the DOM
html = () => "<div id='hello-world'></div>"
// Registers the hint message for the right-click menu
hint = () => "this is hello world hint"
// Registers the shortcut key to trigger the callback. Returns an array of strings
hotkey = () => [this.config.hotkey]
// Used for plugin initialization, typically for getting or setting DOM elements, initializing variables, etc.
init = () => {
// Get the DOM element inserted by the html() method
this.myDiv = document.querySelector("#hello-world")
}
// process method runs automatically after plugin initialization is complete (after executing the above registration logic)
process = () => {
// All configuration items from the TOML file can be accessed via this.config
console.log(this.config.console_message)
console.log("[helloWorldPlugin]: ", this)
console.log(this.myDiv)
}
// callback method is automatically called when clicking a right-click menu option or typing a shortcut key
// Note: If the callback function is not defined, the plugin cannot be triggered by clicking the right-click menu
// The anchorNode parameter represents the Element where the cursor is located when the plugin is called
callback = anchorNode => {
alert(this.config.show_message)
}
}
// Export the plugin class
module.exports = { plugin: helloWorld }
Verification:
- Restart Typora.
- Open Chrome devtools and check if "I am in process", the plugin object, and the corresponding Element are output to the console.
- Right-click the editor area to open the right-click menu. Hover the mouse over
Often Used Plugins -> Secondary Plugins -> hellworld. The hint message "this is hello world hint" should be displayed. Click thehellworldmenu item. A pop-up box should appear, displaying "this is hello world plugin". - Press the shortcut key
ctrl+alt+uyou defined. The same pop-up box should appear.
Insert Mind Map
This example demonstrates how to get the outline structure of the current document and insert it into the document in the form of a Mermaid diagram.
Implementation:
- Step 1: Add the configuration in
./plugin/global/settings/custom_plugin.user.toml. - Step 2: In the
./plugin/custom/pluginsdirectory, create a.jsfile with the same name as the plugin (insertMindmap.js). In this file, create a class that inherits from BaseCustomPlugin and export it asplugin.
# ./plugin/global/settings/custom_plugin.user.toml
[insertMindmap]
name = "InsertMindMap" # Plugin name
enable = true # Whether to enable this secondary plugin
hide = false # Whether to hide it in the right-click menu
order = 1 # Order of appearance in the right-click menu
// ./plugin/custom/plugins/insertMindmap.js
class insertMindmap extends BaseCustomPlugin {
// The callback method gets the document outline tree, converts it to Mermaid format, and inserts it into the document
callback = anchorNode => {
const tree = this.utils.getTocTree() // Get the document outline tree structure
const mermaid = this._toGraph(tree) // Convert the tree structure to Mermaid graph format
this.utils.insertText(null, mermaid) // Insert the generated Mermaid code into the document
}
_toGraph = tree => {
let num = 0
const getName = node => {
if (node._shortName) {
return node._shortName
}
node._shortName = "T" + ++num
const name = node.text.replace(/"/g, "#quot;")
return `${node._shortName}("${name}")`
}
const getTokens = (node, list) => {
node.children.forEach(child => list.push(getName(node), "-->", getName(child), "\n"))
node.children.forEach(child => getTokens(child, list))
return list
}
const tokens = getTokens(tree, ["graph LR", "\n"])
return ["```mermaid", "\n ", ...tokens, "```"].join("")
}
}
// Export the plugin class
module.exports = { plugin: insertMindmap }
Verification:
Open Typora and right-click the editor area to open the right-click menu. Select Often Used Plugins -> Secondary Plugins -> InsertMindMap and click. Based on the outline structure of the current document, a corresponding Mermaid diagram will be inserted into the document.
For example, for a document README-en.md with the following structure:
## custom:Open Capabilities
## Core Features
## How to Use
## Examples
### Quick Start
### Insert Mind Map
### Copy Title Path
The generated Mermaid diagram will look like this:
graph LR
T1("README-en")-->T2("custom:Open Capabilities")
T1-->T3("Core Features")
T1-->T4("How to Use")
T1-->T5("Examples")
T5-->T6("Quick Start")
T5-->T7("Insert Mind Map")
T5-->T8("Copy Title Path")
Copy Title Path
This example demonstrates how to add an option to the right-click menu that copies the full path of the current title to the clipboard when the cursor is on a title.
Implementation:
- Step 1: Add the configuration in
./plugin/global/settings/custom_plugin.user.toml. - Step 2: In the
./plugin/custom/pluginsdirectory, create a.jsfile with the same name as the plugin (myFullPathCopy.js). In this file, create a class that inherits from BaseCustomPlugin and export it asplugin. - Step 3: Verify. Place the cursor on a title, right-click the plugin option or use the shortcut key, and then try pasting to verify that the title path has been copied.
# ./plugin/global/settings/custom_plugin.user.toml
[myFullPathCopy]
name = "CopyFullPath" # Plugin name
enable = true # Whether to enable this secondary plugin
hide = false # Whether to hide it in the right-click menu
order = 1 # Order of appearance in the right-click menu
# Shortcut key
hotkey = "ctrl+shift+u"
# File name used if this plugin is called on a blank page (file does not exist yet, requires a default name)
untitled_file_name = "Untitled"
# Whether to add a space between the title and the hint
add_space = true
// ./plugin/custom/plugins/myFullPathCopy.js
// 1
class myFullPathCopy extends BaseCustomPlugin {
// 2
selector = () => "#write > [cid]"
// 3
hint = () => "Copy the path of the current title to the clipboard"
// 4
init = () => {}
// 5
style = () => {}
// 6
html = () => {}
// 7
hotkey = () => [this.config.hotkey]
// 8
prepare = async () => {}
// 9
process = () => {}
// 10
callback = anchorNode => {
const text = this.getFullPath(anchorNode)
navigator.clipboard.writeText(text)
}
getFullPath = (anchorNode) => {
const getHeaders = (startNode) => {
const HEADING_TAGS = ["H1", "H2", "H3", "H4", "H5", "H6"]
const i18nSuffixes = ["Level1", "Level2", "Level3", "Level4", "Level5", "Level6"]
const i18nNoHeader = "NoHeader"
const headerMap = new Map()
let minLevel = Infinity
let curNode = startNode
while (curNode && minLevel > 0) {
const level = HEADING_TAGS.indexOf(curNode.tagName)
if (level !== -1 && level < minLevel) {
headerMap.set(level, curNode.textContent)
minLevel = level
}
curNode = curNode.previousElementSibling
}
const maxDepth = (headerMap.size === 0) ? 0 : Math.max(...headerMap.keys()) + 1
return Array.from({ length: maxDepth }, (_, level) => {
const title = headerMap.get(level) || i18nNoHeader
const suffix = i18nSuffixes[level]
const space = this.config.add_space ? " " : ""
return title + space + suffix
})
}
const filePath = this.utils.getFilePath() || this.config.untitled_file_name
const pathSegments = getHeaders(anchorNode)
return this.utils.Package.Path.join(filePath, ...pathSegments)
}
}
// 11
module.exports = { plugin: myFullPathCopy }
// 1. Create a class that inherits from the BaseCustomPlugin class. This will automatically give myFullPathCopy the utils and config properties.
// - config: All configuration fields for this plugin in the custom_plugin.user.toml file.
// - utils: Static utility class provided by the plugin system, defined in `./plugin/global/core/utils`.
// 2. selector: Defines the cursor positions where the plugin is available. The plugin is only available when the cursor is on which elements. Returning a null-like value means it is available at any position. Here, it means it is only available when the cursor is on a Markdown heading element.
// 3. hint: The hint message displayed when the mouse hovers over the right-click menu option.
// 4. init: Perform variable initialization operations here.
// 5. style: Used to insert style tags.
// 6. html: Used to insert HTML tags. Returns an Element type or a string representing the element.
// 7. hotkey: Registers a shortcut key for the callback function. Returns an array of strings, or an array of objects in the format [{ hotkey: "", callback: () => console.log(123) }].
// 8. prepare: The first function to be executed, used to initialize data required by the plugin or perform pre-checks. If it returns utils.PLUGIN_LOAD_ABORT, plugin loading is stopped.
// 9. process: Add plugin business logic here.
// 10. callback: The callback function called when the user clicks a right-click menu option or types a shortcut key. The anchorNode parameter represents the Element where the cursor is located.
// 11. Export the plugin class as `plugin`.