Typora插件开发指南:打造专属IDE式写作环境
1. 引言
Typora 以所见即所得的沉浸式写作体验赢得了大量技术写作者的青睐,但原生功能的克制也意味着它缺少代码审查、格式化、定时保存等 IDE 式特性。好在 Typora 的内部架构基于 Electron,并且开放了插件机制,让开发者可以通过 JavaScript 与 CSS 为编辑器注入新能力。
通过插件,你可以把 Typora 变成一套轻量级 Markdown IDE:自动插入代码模板、集成外部 lint 工具、嵌入 Git 面板——甚至实现自己的图表渲染器。本文将从零起步,带你打通插件开发的全流程,从环境搭建到打包发布,每一步都有可运行的代码示例。
2. Typora 插件体系概览
Typora 的插件系统并非独立进程,而是直接运行在编辑器的渲染进程中。理解这一点,对后续开发至关重要:
- 运行环境:Electron 渲染进程,Node.js API 可用,可访问 DOM 与文件系统。
- 语言栈:JavaScript (ES6+)、CSS。
- 插件位置:默认在 Typora 安装目录下的
resources/app/plugin/,也可以配置自定义路径。 - 核心文件:每个插件至少包含一个
main.js作为入口,以及一个可选的style.css。 - 配置:
window.utools或全局Editor对象提供丰富 API(虽无官方完整文档,但可通过内核代码反推)。
插件通过修改 Typora 的 plugin.json 来注册,格式如下:
{
"plugins": [
"my-plugin"
]
}
插件目录结构示例:
my-plugin/
├── main.js // 插件逻辑
├── style.css // 样式定制
└── manifest.json // 元信息(非必需)
启动时 Typora 会依次加载所有已注册插件的 main.js。
3. 开发环境搭建
3.1 基础工具
- Node.js(推荐 v16 以上),确保
node与npm可用。 - 代码编辑器:VS Code 或其他,能写 JS 即可。
- Typora 开发者模式:启动时添加参数
--enable-logging --remote-debugging-port=9222,方便调试。
Windows 下可通过命令行启动:
typora.exe --enable-logging
macOS 可通过终端:
open /Applications/Typora.app --args --enable-logging
3.2 开启开发者工具
Typora 本身是 Electron 应用,按 Ctrl+Shift+I(Windows/Linux)或 Cmd+Option+I(macOS)可打开开发者工具,里面的 Console 就是你写插件时的调试利器。
3.3 配置插件目录
为了避免修改安装目录带来权限问题,推荐创建一个独立插件文件夹,通过 Typora 的 preferences(偏好设置)中的高级选项指定外部插件路径。然后在指定文件夹下创建你的插件子目录。
4. 第一个插件:Hello World
我们现在写一个极简插件,功能:在「编辑」菜单中添加一个菜单项,点击后弹出一条提示。
4.1 目录与文件
在插件根目录下创建 hello-world 文件夹,内部写入三个文件:
main.jsstyle.css(暂时留空)manifest.json
manifest.json:
{
"name": "hello-world",
"version": "1.0.0",
"description": "My first Typora plugin",
"author": "Your Name",
"main": "main.js"
}
main.js:
// 插件入口
module.exports = {
activate() {
console.log('Hello World 插件已激活');
},
deactivate() {
console.log('Hello World 插件已卸载');
},
// 其他扩展点
};
对于早期 Typora 版本,入口可能直接是一段自执行函数,将代码挂载到 window 或通过 utools 注册。若你的版本不支持模块系统,可以这样写:
window.addEventListener('load', () => {
const { Menu } = require('electron').remote;
const menu = Menu.getApplicationMenu();
const editMenu = menu.items.find(i => i.label === '编辑');
editMenu.submenu.append(new (require('electron').remote.MenuItem)({
label: 'Say Hello',
click: () => {
alert('Hello from plugin!');
}
}));
Menu.setApplicationMenu(menu);
console.log('Hello World 注入成功');
});
4.2 注册插件
在 Typora 的 plugin.json 中加入 "hello-world",重启 Typora。打开菜单「编辑」→ 应该能看到「Say Hello」,点击即弹窗。
5. 核心 API 解析
Typora 向外暴露的 API 没有完整的官方文档,但通过调试工具可以发现几个关键对象。
5.1 Editor 全局对象
window.editor 是核心。常用方法:
// 获取当前编辑器中的 Markdown 源码
const content = editor.getValue();
// 替换选中文本或光标处插入
editor.replaceSelection('**粗体文本**');
// 获取选区信息
const range = editor.getSelection();
// → {start: {line, ch}, end: {line, ch}}
// 监听文本变化
editor.on('change', (cm, changeObj) => {
// changeObj 包含 from, to, text, removed 等
});
// 获取光标所在行
const line = editor.getLine(editor.getCursor().line);
这些 API 都来自 CodeMirror,因为 Typora 的编辑内核就是 CodeMirror。
5.2 文件系统访问
通过 Node.js 的 fs 模块,你可以读写任意文件(前提是权限允许):
const fs = require('fs');
const path = require('path');
// 读取当前正在编辑的完整路径
const filePath = window.__typora_file_path__; // 非标准属性,某些版本可用
// 如果没有,可以通过文档对象获取
const currentFile = document.querySelector('title').innerText; // 标题即文件名
小心处理文件读写,避免覆盖用户内容。
5.3 UI 定制
- 菜单注入:如 Hello World 示例所示,通过
electron.remote.Menu动态修改菜单。 - 工具栏按钮:Typora 的工具栏是 HTML 元素,可以通过 DOM 操作插入新按钮:
const toolbar = document.querySelector('.ty-toolbar');
const btn = document.createElement('button');
btn.innerHTML = '⭐ 收藏';
btn.onclick = () => { /* 你的逻辑 */ };
toolbar.appendChild(btn);
- 面板:可模仿 Typora 侧边栏创建浮动面板,需要编写相应 HTML/CSS,并链接到插件
style.css中。
5.4 快捷键注册
可以通过 editor.addKeyMap 绑定自定义快捷键:
editor.addKeyMap({
'Ctrl-B': function(cm) {
cm.replaceSelection('**' + cm.getSelection() + '**');
}
});
6. 实战:代码片段插入器
目标:打造一个常用代码片段快速插入的插件,支持按语言分类浏览,选中一行代码模板即可插入到光标位置。
6.1 数据结构与界面
设计片段 JSON 文件:
{
"python": [
{"name": "读取文件", "code": "with open('${1:file}', 'r') as f:\n data = f.read()"},
{"name": "列表推导式", "code": "[x for x in range(${1:10})]"}
],
"javascript": [
{"name": "箭头函数", "code": "const ${1:fn} = (${2:args}) => { ${3} }"}
]
}
${1:placeholder} 语法借鉴了 VS Code 的代码片段,插入后光标可跳转占位符。
6.2 实现面板
在 main.js 中创建侧边栏面板:
function createSnippetPanel() {
const panel = document.createElement('div');
panel.id = 'snippet-panel';
panel.innerHTML = `
<select id="lang-select"></select>
<ul id="snippet-list"></ul>
`;
document.body.appendChild(panel);
// 加载 JSON 片段数据
const snippets = require('./snippets.json');
const select = panel.querySelector('#lang-select');
Object.keys(snippets).forEach(lang => {
const opt = document.createElement('option');
opt.value = lang;
opt.textContent = lang;
select.appendChild(opt);
});
select.addEventListener('change', () => {
const lang = select.value;
const list = panel.querySelector('#snippet-list');
list.innerHTML = '';
snippets[lang].forEach(s => {
const li = document.createElement('li');
li.textContent = s.name;
li.addEventListener('click', () => {
const editor = window.editor;
const text = s.code.replace(/\$\{\d+:.*?\}/g, ''); // 可后续扩展占位符跳转
editor.replaceSelection(text);
});
list.appendChild(li);
});
});
select.dispatchEvent(new Event('change'));
}
6.3 注册快捷键打开面板
editor.addKeyMap({
'Ctrl-Shift-J': function() {
const panel = document.getElementById('snippet-panel');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
}
});
6.4 样式美化(style.css)
#snippet-panel {
position: fixed;
right: 10px;
top: 60px;
width: 260px;
background: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
padding: 10px;
z-index: 1000;
display: none;
}
#snippet-panel select {
width: 100%;
margin-bottom: 8px;
}
#snippet-list {
list-style: none;
padding: 0;
}
#snippet-list li {
cursor: pointer;
padding: 4px 8px;
border-bottom: 1px solid #eee;
}
#snippet-list li:hover {
background: #ddd;
}
现在,重启 Typora,按下 Ctrl+Shift+J 即可呼唤出代码片段面板,点击任何片段,它就会插入到编辑器当前光标处。
7. 高级玩法:集成外部工具
Typora 插件可以化身胶水,串联本地或在线工具。
7.1 调用 Prettier 格式化 Markdown
const { execSync } = require('child_process');
function formatMarkdown() {
const content = editor.getValue();
try {
const formatted = execSync('npx prettier --parser markdown', {
input: content,
encoding: 'utf-8',
});
editor.setValue(formatted);
console.log('格式化完成');
} catch (e) {
console.error('格式化出错', e);
}
}
绑定到快捷键:
editor.addKeyMap({
'Ctrl-Shift-F': formatMarkdown,
});
7.2 远程请求与 AI 辅助
你可以直接在插件里发起 HTTP 请求(需要引入 axios 或使用 net 模块),例如调用 OpenAI 接口生成摘要、翻译段落等。注意保护好 API Key,可提示用户配置在单独文件中。
7.3 自定义渲染
Typora 支持通过 markdown-it 扩展 Markdown 解析。例如,为自定义块语法转成高亮框:
// 需要在插件中重写 Typora 的解析器
const md = require('markdown-it')();
md.use((md) => {
md.block.ruler.before('fence', 'mylang', function (state, startLine, endLine, silent) {
// 解析 ::: tip ... ::: 并返回 token
});
});
但直接修改 Typora 的内部解析器存在版本兼容风险,需谨慎。
8. 打包与分发
写完插件后,自然希望分享给其他 Typora 用户。
8.1 打包为 ZIP
最简单的分发方式:将插件文件夹压缩为 ZIP,并附上安装说明。用户在 Typora 的偏好设置中导入外部插件文件夹,或手动移动到插件目录。
8.2 使用 asar 打包(可选)
Typora 本身用 asar 打包资源,你也可以将插件打包成 plugin.asar,放到资源目录中。但跨平台兼容需自行测试。
8.3 发布到社区
- 将源码上传到 GitHub,书写清晰的 README。
- 在 Typora 官方拓展示例仓库提交 PR,或在论坛发文推荐。
- 可利用 GitHub Release 附带 ZIP 下载。
8.4 考虑兼容性
- 不同 Typora 版本的内核变动较大(尤其是 CodeMirror 升级),最好标明支持的最低版本。
- 尽量使用稳定的全局 API,避免过多依赖内部私有属性。
- 在
package.json中声明依赖并指明安装要求。
9. 总结
通过以上步骤,你已经掌握 Typora 插件开发的核心要诀:从简单的菜单注入到复杂的面板交互,再到集成外部工具链。每一行插件都是对 Typora 环境的个性化重塑,让它从一款纯粹的编辑器蜕变为你的专属 Markdown IDE。
建议你从一个小而美的痛点入手——比如图片自动压缩、TODO 统计面板——逐步迭代,享受创造工具的快感。代码即创意,开始动手吧!
更多推荐
所有评论(0)