编写第一个效率插件
完成文本整理工具,从五个文件到实际运行。
本页目录
目标与最终效果#
这一章完成一个可以在独立窗口打开的 文本整理效率插件。用户输入文字,点击按钮后合并连续空白;关闭或停用时移除自己的界面和事件。
示例不请求网络、不读写文件、不依赖模型账号,便于先验证扩展机制。下载源码后,代码与本页引用来自同一份示例。
建立目录#
my-example-tool/
manifest.json
main.js
modules/
text.js
view.js
styles.css
.tests/text.test.cjs
README.txt按环境准备把目录放进开发版对应的工具扩展目录,重新扫描并显式启用。入口文件不能放在多套一层的子目录里。
第一步:声明清单#
{
"id": "my-example-tool",
"name": "文本整理示例",
"version": "1.0.0",
"apiVersion": 1,
"minAppVersion": "2.7.5",
"author": "Your Name",
"description": "一个包含完整打开与清理流程的示例工具",
"category": "tool",
"type": "thirdparty",
"main": "main.js",
"scripts": [
"modules/text.js",
"modules/view.js"
],
"styles": [
"styles.css"
],
"permissions": [],
"slot": "workshop",
"button": {
"label": "文本整理",
"order": 100
}
}纯逻辑先加载,视图后加载,最后由 main 注册。permissions 留空,因为本例不调用文件或模型能力;不需要的能力不要提前声明。
第二步:分离可测试的业务逻辑#
(function () {
window.MyExampleTool = window.MyExampleTool || {};
MyExampleTool.normalize = function (value) {
return String(value).replace(/\s+/g, ' ').trim();
};
})();将文本处理与 DOM 分开后,输入边界可以直接用 Node 测试。这里使用自己的 MyExampleTool 命名空间,避免覆盖宿主对象。
第三步:创建界面并保存清理函数#
(function () {
window.MyExampleTool = window.MyExampleTool || {};
var cleanup = null;
MyExampleTool.dispose = function () {
if (cleanup) cleanup();
cleanup = null;
};
MyExampleTool.render = function (host) {
MyExampleTool.dispose();
if (!host) throw new Error('没有可用的插件容器');
var root = document.createElement('section');
root.className = 'my-example-tool';
var title = document.createElement('h2');
title.textContent = '文本整理';
var input = document.createElement('textarea');
input.placeholder = '在这里粘贴需要整理的文字';
input.setAttribute('aria-label', '待整理文本');
var button = document.createElement('button');
button.type = 'button';
button.textContent = '整理空白';
var status = document.createElement('p');
status.setAttribute('role', 'status');
function run() {
input.value = MyExampleTool.normalize(input.value);
status.textContent = '整理完成,结果保留在输入框中。';
}
button.addEventListener('click', run);
root.append(title, input, button, status);
host.appendChild(root);
cleanup = function () {
button.removeEventListener('click', run);
root.remove();
};
};
})();请注意四个细节:
- 只在
host中追加自己的根节点,不替换宿主整个页面。 - 用户文本通过
value或textContent处理,不拼成 HTML。 - 监听函数保留引用,在
dispose中精确移除。 - 再次 render 前先 dispose,避免重复按钮和重复事件。
第四步:注册生命周期#
(function () {
Mrite.plugins.register({
id: 'my-example-tool',
activate: function (api, hostElement) {
MyExampleTool.render(hostElement);
},
deactivate: function () {
MyExampleTool.dispose();
}
});
})();activate(api, hostElement) 由宿主调用,api 是当前窗口的实际桥接对象,第二个参数是挂载节点。入口注册阶段不应该提前运行文件操作或启动后台任务。
deactivate() 负责回收插件自己的资源。未来增加请求、订阅或编辑器时,也要将其清理放进这一条生命周期链。
第五步:限制样式范围#
.my-example-tool { padding: 24px; color: #172033; }
.my-example-tool h2 { margin: 0 0 16px; }
.my-example-tool textarea {
display: block; width: 100%; min-height: 180px;
box-sizing: border-box; padding: 12px; resize: vertical;
border: 1px solid #b9c6d8; border-radius: 8px;
font: inherit; background: #fff; color: #172033;
}
.my-example-tool button {
margin-top: 12px; padding: 9px 16px;
border: 0; border-radius: 7px; background: #245dba;
color: #fff; cursor: pointer;
}
.my-example-tool p { line-height: 1.7; }所有规则从 .my-example-tool 开始。避免直接改 body、通用 .panel 或全局 button。长内容在自己的区域中换行或滚动,不能撑宽整个桌面窗口。
第六步:运行与验证#
- 扫描、启用并打开
my-example-tool。 - 输入
alpha beta,点击后得到alpha beta。 - 输入空白、中文、多行文字和已有正常文本,观察结果。
- 关闭后重新打开,确认只有一个按钮。
- 停用再启用,确认没有旧事件继续触发。
然后在示例目录运行:
node --test .tests/text.test.cjs接下来增加哪一项能力#
需要连接真实文件时,继续阅读文件接口;需要保存编辑状态和处理异步回调时,阅读生命周期。每次增加一个能力,先核对目标宿主是否存在对应方法,再测试失败和取消路径。