Files
mnote/wolai-frontend/public/onlyoffice/plugins/agent-tools/plugin.js
T
lix-2026 b33ffb99e7 feat: 收口文档桥接与 OnlyOffice/Sidebar 回归
- 为 documents.save/meta/content、blocks.patch 与 sidebar.dataset.list 补齐 Rust 协议映射、共享契约与桥接执行器

- 对齐 BlockNote、Mindmap、OnlyOffice 的保存/路由元信息,并补真实浏览器回归脚本与 OnlyOffice 部署基线

- 忽略 Rust 本地构建产物与 Harness 调试状态文件,避免临时产物进入仓库历史
2026-04-15 03:06:29 +08:00

150 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 说明:该插件用于把 ONLYOFFICE 编辑器“选区读/写”能力暴露给宿主页面(MNOTE)。
// 宿主页面通过 postMessage 下发 oo_* 工具调用;插件执行后再 postMessage 回传结果。
(function () {
const CHANNEL = "mnote_onlyoffice_agent_tools_v1";
let readyPulseTimer = null;
let readyPulseDeadline = 0;
const safePostToTop = (payload) => {
try {
if (typeof window === "undefined") return;
if (!window.top) return;
window.top.postMessage({ channel: CHANNEL, ...payload }, "*");
} catch {
// ignore
}
};
const isPluginApiReady = () =>
Boolean(window.Asc && window.Asc.plugin && typeof window.Asc.plugin.executeMethod === "function");
const stopReadyPulse = () => {
if (readyPulseTimer) {
window.clearInterval(readyPulseTimer);
readyPulseTimer = null;
}
readyPulseDeadline = 0;
};
const startReadyPulse = () => {
if (readyPulseTimer) return;
readyPulseDeadline = Date.now() + 20_000;
safePostToTop({ type: "ready" });
readyPulseTimer = window.setInterval(() => {
if (Date.now() > readyPulseDeadline) {
stopReadyPulse();
return;
}
safePostToTop({ type: "ready" });
}, 1000);
};
const waitForPluginApiReady = () => {
if (isPluginApiReady()) {
startReadyPulse();
return;
}
const deadline = Date.now() + 60_000;
const timer = window.setInterval(() => {
if (isPluginApiReady()) {
window.clearInterval(timer);
startReadyPulse();
return;
}
if (Date.now() > deadline) {
window.clearInterval(timer);
safePostToTop({ type: "result", callId: "bridge_bootstrap", ok: false, error: "ONLYOFFICE 插件 API 长时间未就绪" });
}
}, 500);
};
const execMethod = (method, args) =>
new Promise((resolve, reject) => {
try {
if (!window.Asc || !window.Asc.plugin) throw new Error("ONLYOFFICE 插件 API 未就绪");
window.Asc.plugin.executeMethod(method, Array.isArray(args) ? args : [], (res) => resolve(res));
} catch (e) {
reject(e instanceof Error ? e : new Error(String(e)));
}
});
const handleTool = async (tool, args) => {
const a = args && typeof args === "object" ? args : {};
if (tool === "oo_get_selection") {
// 说明:先做最基础的“纯文本选区读取”,HTML 可在后续升级(需要更复杂的导出方式)。
const text = await execMethod("GetSelectedText", []);
return { format: "text", text: String(text ?? "") };
}
if (tool === "oo_replace_selection") {
const format = String(a.format ?? "text");
const text = String(a.text ?? "");
if (format === "html") {
await execMethod("PasteHtml", [text]);
return { ok: true, format: "html", length: text.length };
}
await execMethod("PasteText", [text]);
return { ok: true, format: "text", length: text.length };
}
if (tool === "oo_insert_text") {
const text = String(a.text ?? "");
await execMethod("PasteText", [text]);
return { ok: true, length: text.length };
}
if (tool === "oo_insert_html") {
const html = String(a.html ?? "");
await execMethod("PasteHtml", [html]);
return { ok: true, length: html.length };
}
if (tool === "oo_insert_image") {
// 说明:优先用 HTML 插入 <img>,避免依赖 PutImageDataToSelection 的参数差异。
const src = String(a.src ?? a.imageData ?? "");
if (!src) throw new Error("缺少图片数据(src/imageData");
const w = Number(a.width ?? 0);
const h = Number(a.height ?? 0);
const widthAttr = Number.isFinite(w) && w > 0 ? ` width=\"${Math.floor(w)}\"` : "";
const heightAttr = Number.isFinite(h) && h > 0 ? ` height=\"${Math.floor(h)}\"` : "";
const html = `<img src=\"${src}\"${widthAttr}${heightAttr} />`;
await execMethod("PasteHtml", [html]);
return { ok: true };
}
throw new Error(`未知工具:${tool}`);
};
window.Asc = window.Asc || {};
window.Asc.plugin = window.Asc.plugin || {};
window.Asc.plugin.init = function () {
startReadyPulse();
};
waitForPluginApiReady();
window.addEventListener("message", async (ev) => {
const msg = ev && ev.data ? ev.data : null;
if (!msg || typeof msg !== "object") return;
if (msg.channel !== CHANNEL) return;
if (msg.type !== "call") return;
stopReadyPulse();
const callId = String(msg.callId ?? "").trim();
const tool = String(msg.tool ?? "").trim();
const args = msg.args && typeof msg.args === "object" ? msg.args : {};
if (!callId || !tool) return;
try {
const result = await handleTool(tool, args);
safePostToTop({ type: "result", callId, ok: true, result });
} catch (e) {
const err = e instanceof Error ? e.message : String(e);
safePostToTop({ type: "result", callId, ok: false, error: err });
}
});
})();