编写第一个效率插件

完成文本整理工具,从五个文件到实际运行。

扩展 API v1按当前源码核对
本页目录

目标与最终效果#

这一章完成一个可以在独立窗口打开的 文本整理效率插件。用户输入文字,点击按钮后合并连续空白;关闭或停用时移除自己的界面和事件。

示例不请求网络、不读写文件、不依赖模型账号,便于先验证扩展机制。下载源码后,代码与本页引用来自同一份示例。

下载完整效率插件源码

建立目录#

text
my-example-tool/
  manifest.json
  main.js
  modules/
    text.js
    view.js
  styles.css
  .tests/text.test.cjs
  README.txt

按环境准备把目录放进开发版对应的工具扩展目录,重新扫描并显式启用。入口文件不能放在多套一层的子目录里。

第一步:声明清单#

json
{
  "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 留空,因为本例不调用文件或模型能力;不需要的能力不要提前声明。

第二步:分离可测试的业务逻辑#

javascript
(function () {
  window.MyExampleTool = window.MyExampleTool || {};
  MyExampleTool.normalize = function (value) {
    return String(value).replace(/\s+/g, ' ').trim();
  };
})();

将文本处理与 DOM 分开后,输入边界可以直接用 Node 测试。这里使用自己的 MyExampleTool 命名空间,避免覆盖宿主对象。

第三步:创建界面并保存清理函数#

javascript
(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();
    };
  };
})();

请注意四个细节:

  1. 只在 host 中追加自己的根节点,不替换宿主整个页面。
  2. 用户文本通过 value 或 textContent 处理,不拼成 HTML。
  3. 监听函数保留引用,在 dispose 中精确移除。
  4. 再次 render 前先 dispose,避免重复按钮和重复事件。

第四步:注册生命周期#

javascript
(function () {
  Mrite.plugins.register({
    id: 'my-example-tool',
    activate: function (api, hostElement) {
      MyExampleTool.render(hostElement);
    },
    deactivate: function () {
      MyExampleTool.dispose();
    }
  });
})();

activate(api, hostElement) 由宿主调用,api 是当前窗口的实际桥接对象,第二个参数是挂载节点。入口注册阶段不应该提前运行文件操作或启动后台任务。

deactivate() 负责回收插件自己的资源。未来增加请求、订阅或编辑器时,也要将其清理放进这一条生命周期链。

第五步:限制样式范围#

css
.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。长内容在自己的区域中换行或滚动,不能撑宽整个桌面窗口。

第六步:运行与验证#

  1. 扫描、启用并打开 my-example-tool。
  2. 输入 alpha beta ,点击后得到 alpha beta。
  3. 输入空白、中文、多行文字和已有正常文本,观察结果。
  4. 关闭后重新打开,确认只有一个按钮。
  5. 停用再启用,确认没有旧事件继续触发。

然后在示例目录运行:

powershell
node --test .tests/text.test.cjs

接下来增加哪一项能力#

需要连接真实文件时,继续阅读文件接口;需要保存编辑状态和处理异步回调时,阅读生命周期。每次增加一个能力,先核对目标宿主是否存在对应方法,再测试失败和取消路径。

下载本页 Markdown