diff --git a/docs.json b/docs.json index dc520737..555f33c4 100644 --- a/docs.json +++ b/docs.json @@ -532,6 +532,7 @@ "custom-nodes/js/javascript_selection_toolbox", "custom-nodes/js/javascript_commands_keybindings", "custom-nodes/js/javascript_topbar_menu", + "custom-nodes/js/context-menu-migration", "custom-nodes/js/javascript_examples", "custom-nodes/i18n" ] @@ -1099,6 +1100,7 @@ "zh-CN/custom-nodes/js/javascript_selection_toolbox", "zh-CN/custom-nodes/js/javascript_commands_keybindings", "zh-CN/custom-nodes/js/javascript_topbar_menu", + "zh-CN/custom-nodes/js/context-menu-migration", "zh-CN/custom-nodes/js/javascript_examples", "zh-CN/custom-nodes/i18n" ] diff --git a/zh-CN/custom-nodes/js/context-menu-migration.mdx b/zh-CN/custom-nodes/js/context-menu-migration.mdx new file mode 100644 index 00000000..a0c1ebba --- /dev/null +++ b/zh-CN/custom-nodes/js/context-menu-migration.mdx @@ -0,0 +1,319 @@ +--- +title: "上下文菜单迁移指南" +--- + +本指南帮助您从已弃用的猴子补丁(monkey-patching)方法迁移到新的上下文菜单扩展 API。 + +旧的猴子补丁方法修改 `LGraphCanvas.prototype.getCanvasMenuOptions` 和 `nodeType.prototype.getExtraMenuOptions` 已被弃用: + +如果您在浏览器控制台中看到弃用警告,说明您的扩展正在使用旧 API,应该进行迁移。 + +## 迁移画布菜单 + +### 旧方法(已弃用) + +旧方法在扩展设置期间修改原型: + +```javascript +import { app } from "../../scripts/app.js" + +app.registerExtension({ + name: "MyExtension", + async setup() { + // ❌ 旧方法: 猴子补丁原型 + const original = LGraphCanvas.prototype.getCanvasMenuOptions + LGraphCanvas.prototype.getCanvasMenuOptions = function() { + const options = original.apply(this, arguments) + + options.push(null) // 分隔符 + options.push({ + content: "我的自定义操作", + callback: () => { + console.log("操作已触发") + } + }) + + return options + } + } +}) +``` + +### 新方法(推荐) + +新方法使用专用的扩展钩子: + +```javascript +import { app } from "../../scripts/app.js" + +app.registerExtension({ + name: "MyExtension", + // ✅ 新方法: 使用 getCanvasMenuItems 钩子 + getCanvasMenuItems(canvas) { + return [ + null, // 分隔符 + { + content: "我的自定义操作", + callback: () => { + console.log("操作已触发") + } + } + ] + } +}) +``` + +### 主要区别 + +| 旧方法 | 新方法 | +|--------|--------| +| 在 `setup()` 中修改 | 使用 `getCanvasMenuItems()` 钩子 | +| 包装现有函数 | 直接返回菜单项 | +| 修改 `options` 数组 | 返回新数组 | +| 通过 `this` 访问画布 | 画布作为参数传递 | + +## 迁移节点菜单 + +### 旧方法(已弃用) + +旧方法修改节点类型原型: + +```javascript +import { app } from "../../scripts/app.js" + +app.registerExtension({ + name: "MyExtension", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeType.comfyClass === "KSampler") { + // ❌ 旧方法: 猴子补丁节点原型 + const original = nodeType.prototype.getExtraMenuOptions + nodeType.prototype.getExtraMenuOptions = function(canvas, options) { + original?.apply(this, arguments) + + options.push({ + content: "随机化种子", + callback: () => { + const seedWidget = this.widgets.find(w => w.name === "seed") + if (seedWidget) { + seedWidget.value = Math.floor(Math.random() * 1000000) + } + } + }) + } + } + } +}) +``` + +### 新方法(推荐) + +新方法使用专用的扩展钩子: + +```javascript +import { app } from "../../scripts/app.js" + +app.registerExtension({ + name: "MyExtension", + // ✅ 新方法: 使用 getNodeMenuItems 钩子 + getNodeMenuItems(node) { + const items = [] + + // 仅为特定节点类型添加项目 + if (node.comfyClass === "KSampler") { + items.push({ + content: "随机化种子", + callback: () => { + const seedWidget = node.widgets.find(w => w.name === "seed") + if (seedWidget) { + seedWidget.value = Math.floor(Math.random() * 1000000) + } + } + }) + } + + return items + } +}) +``` + +### 主要区别 + +| 旧方法 | 新方法 | +|--------|--------| +| 在 `beforeRegisterNodeDef()` 中修改 | 使用 `getNodeMenuItems()` 钩子 | +| 通过 `if` 检查指定类型 | 在钩子中通过 `if` 检查指定类型 | +| 修改 `options` 数组 | 返回新数组 | +| 通过 `this` 访问节点 | 节点作为参数传递 | + +## 常见模式 + +### 条件菜单项 + +两种方法都支持条件项,但新 API 更简洁: + +```javascript +// ✅ 新方法: 简洁的条件逻辑 +getCanvasMenuItems(canvas) { + const items = [] + + if (canvas.selectedItems.size > 0) { + items.push({ + content: `处理 ${canvas.selectedItems.size} 个选中的节点`, + callback: () => { + // 处理节点 + } + }) + } + + return items +} +``` + +### 添加分隔符 + +两种方法中添加分隔符的方式相同: + +```javascript +getCanvasMenuItems(canvas) { + return [ + null, // 分隔符(水平线) + { + content: "我的操作", + callback: () => {} + } + ] +} +``` + +### 创建子菜单 + +创建子菜单的推荐方式是使用声明式的 `submenu` 属性: + +```javascript +getNodeMenuItems(node) { + return [ + { + content: "高级选项", + submenu: { + options: [ + { content: "选项 1", callback: () => {} }, + { content: "选项 2", callback: () => {} } + ] + } + } + ] +} +``` + +这种声明式方法更简洁,并且与 ComfyUI 代码库中使用的模式一致。 + +虽然也支持使用 `has_submenu: true` 和 `new LiteGraph.ContextMenu()` 的基于回调的方法,但为了更好的可维护性,推荐使用声明式的 `submenu` 属性。 + +### 访问状态 + +```javascript +// ✅ 新方法: 状态访问更清晰 +getCanvasMenuItems(canvas) { + // 访问画布属性 + const selectedCount = canvas.selectedItems.size + const graphMousePos = canvas.graph_mouse + + return [/* 菜单项 */] +} + +getNodeMenuItems(node) { + // 访问节点属性 + const nodeType = node.comfyClass + const isDisabled = node.mode === 2 + const widgets = node.widgets + + return [/* 菜单项 */] +} +``` + +## 故障排除 + +### 如何识别旧 API 的使用 + +在您的代码中查找这些模式: + +```javascript +// ❌ 旧 API 的标志: +LGraphCanvas.prototype.getCanvasMenuOptions = function() { /* ... */ } +nodeType.prototype.getExtraMenuOptions = function() { /* ... */ } +``` + +### 理解弃用警告 + +如果您在控制台中看到此警告: + +``` +[DEPRECATED] Monkey-patching getCanvasMenuOptions is deprecated. (Extension: "MyExtension") +Please use the new context menu API instead. +See: https://docs.comfy.org/custom-nodes/js/context-menu-migration +``` + +说明您的扩展正在使用旧方法,应该进行迁移。 + +### 验证迁移成功 + +迁移后: + +1. 从 `setup()` 和 `beforeRegisterNodeDef()` 中删除所有原型修改 +2. 添加 `getCanvasMenuItems()` 和/或 `getNodeMenuItems()` 钩子 +3. 测试您的菜单项是否仍然正确显示 +4. 验证控制台中没有出现弃用警告 + +### 完整迁移示例 + +**迁移前:** + +```javascript +app.registerExtension({ + name: "MyExtension", + async setup() { + const original = LGraphCanvas.prototype.getCanvasMenuOptions + LGraphCanvas.prototype.getCanvasMenuOptions = function() { + const options = original.apply(this, arguments) + options.push({ content: "操作", callback: () => {} }) + return options + } + }, + async beforeRegisterNodeDef(nodeType) { + if (nodeType.comfyClass === "KSampler") { + const original = nodeType.prototype.getExtraMenuOptions + nodeType.prototype.getExtraMenuOptions = function(_, options) { + original?.apply(this, arguments) + options.push({ content: "节点操作", callback: () => {} }) + } + } + } +}) +``` + +**迁移后:** + +```javascript +app.registerExtension({ + name: "MyExtension", + getCanvasMenuItems(canvas) { + return [ + { content: "操作", callback: () => {} } + ] + }, + getNodeMenuItems(node) { + if (node.comfyClass === "KSampler") { + return [ + { content: "节点操作", callback: () => {} } + ] + } + return [] + } +}) +``` + +## 其他资源 + +- [注释示例](./javascript_examples) - 更多使用新 API 的示例 +- [扩展钩子](./javascript_hooks) - 可用扩展钩子的完整列表 +- [命令和快捷键](./javascript_commands_keybindings) - 为您的菜单操作添加键盘快捷键