Files
uinodes-ComfyUI-uinodesDOC/web/uinodesDoc.js
T

232 lines
6.2 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.
import { app } from "../../../scripts/app.js";
function normalize_node_name(node_name) {
if (!node_name) return '';
let text = String(node_name).normalize('NFKC');
// 替换加减乘除为英文
const symbolMap = {
'+': 'plus',
'*': 'times',
'×': 'times',
'/': 'div',
'÷': 'div',
};
for (const [symbol, replacement] of Object.entries(symbolMap)) {
const escapedSymbol = symbol.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
const regex = new RegExp(escapedSymbol, 'g');
text = text.replace(regex, replacement);
}
// 将所有非字母数字(包括空格、标点等)替换为 `-`
text = text.replace(/[^a-zA-Z0-9]+/g, '-');
// 去除首尾和重复的连字符
text = text.replace(/-+/g, '-').replace(/^-|-$/g, '');
return text;
}
/** 根据 python_module 和节点名构建 URL */
function buildNodeUrl(python_module, nodeName) {
if (!python_module || !nodeName) return null;
let category = '';
if (python_module === "nodes") {
category = "nodes";
} else if (python_module.startsWith("comfy_extras.")) {
category = "extra_nodes";
} else if (python_module.startsWith("custom_nodes.")) {
const parts = python_module.split(".");
if (parts.length >= 2) {
category = parts[1]; // 取第二个部分作为仓库名
} else {
category = "custom_nodes";
}
} else if (python_module.startsWith("comfy_api_nodes.")) {
category = "nodes_api";
} else {
return null;
}
const slug = normalize_node_name(nodeName);
return `https://uinodes.com/plugins/${category}/${slug}`;
}
// 部分代码参考kjnodes https://github.com/kijai/ComfyUI-KJNodes
app.registerExtension({
name: "fsy.Link2uinodesCOM",
async beforeRegisterNodeDef(nodeType, nodeData) {
const cnt = 0;
try {
addLinkIcon(nodeData, nodeType);
cnt++;
if (cnt == 1) {
console.log("nodeData", nodeData);
console.log("nodeType", nodeType);
}
} catch (error) {
console.error("Error in registering fsy.Link2uinodesCOM", error);
}
},
});
/** 计算图标位置 */
function getIconPosition(nodeSize, iconSize, iconMargin, position) {
const positions = {
'left_of_help': {
x: nodeSize[0] - iconSize - iconMargin - 18 - 5,
y: iconSize - 34
},
'top_right': {
x: nodeSize[0] - iconSize - iconMargin,
y: iconSize - 34 - 20
},
'top_left': {
x: iconMargin,
y: iconSize - 34
}
};
return positions[position] || positions['left_of_help'];
}
/** 为节点添加链接图标 */
export const addLinkIcon = (
nodeData,
nodeType,
opts = { icon_size: 14, icon_margin: 4 }
) => {
opts = opts || {}
const iconSize = opts.icon_size ? opts.icon_size : 14
const iconMargin = opts.icon_margin ? opts.icon_margin : 4
const drawFg = nodeType.prototype.onDrawForeground
nodeType.prototype.onDrawForeground = function (ctx) {
const r = drawFg ? drawFg.apply(this, arguments) : undefined
if (this.flags.collapsed) return r
// 获取图标位置
const position = app.ui.settings.getSettingValue("fsy.Link2uinodesCOM") || 'left_of_help';
const iconPos = getIconPosition(this.size, iconSize, iconMargin, position);
// 绘制链接图标
ctx.save()
ctx.translate(iconPos.x - 2, iconPos.y)
ctx.scale(iconSize / 32, iconSize / 32)
ctx.strokeStyle = 'rgba(255,255,255,0.3)'
ctx.lineCap = 'round'
ctx.lineJoin = 'round'
ctx.lineWidth = 2.4
ctx.font = 'bold 36px monospace'
ctx.fillStyle = '#4A9EFF'; // 蓝色链接图标
ctx.fillText('🔗', 0, 24) // 使用链接emoji
ctx.restore()
return r
}
// 处理点击事件
const mouseDown = nodeType.prototype.onMouseDown
nodeType.prototype.onMouseDown = function (e, localPos, canvas) {
const r = mouseDown ? mouseDown.apply(this, arguments) : undefined
// 获取图标位置
const position = app.ui.settings.getSettingValue("fsy.Link2uinodesCOM") || 'left_of_help';
const iconPos = getIconPosition(this.size, iconSize, iconMargin, position);
// 检查是否点击了链接图标
if (
localPos[0] > iconPos.x &&
localPos[0] < iconPos.x + iconSize &&
localPos[1] > iconPos.y &&
localPos[1] < iconPos.y + iconSize
) {
// 构建 URL 链接
const url = buildNodeUrl(nodeData.python_module, nodeData.name);
// console.log("构建的URL:", url);
// 打开链接前确认
if (url) {
const confirmed = confirm(`是否要打开以下链接?\n\n${url}\n\n点击"确定"将在新标签页中打开此链接。`);
if (confirmed) {
window.open(url, '_blank');
}
}
return true; // 阻止事件继续传播
}
return r;
}
// 添加悬停提示
const onMouseMove = nodeType.prototype.onMouseMove
nodeType.prototype.onMouseMove = function (e, localPos, canvas) {
const r = onMouseMove ? onMouseMove.apply(this, arguments) : undefined;
const position = app.ui.settings.getSettingValue("fsy.Link2uinodesCOM") || 'left_of_help';
const iconPos = getIconPosition(this.size, iconSize, iconMargin, position);
// 检查鼠标是否悬停在链接图标上
if (
localPos[0] > iconPos.x &&
localPos[0] < iconPos.x + iconSize &&
localPos[1] > iconPos.y &&
localPos[1] < iconPos.y + iconSize
) {
// 设置鼠标样式为指针
canvas.canvas.style.cursor = 'pointer';
// 构建URL并设置悬停提示
const url = buildNodeUrl(nodeData.python_module, nodeData.name);
if (url) {
canvas.canvas.title = `即将打开 ${url}`;
}
} else {
// 移除悬停提示
canvas.canvas.title = '';
}
return r;
}
/*
updated: 2025-08-10
description: 标题过大无法触发点击,添加右键菜单事件进行网站导航
*/
const original_getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function(_, options) {
original_getExtraMenuOptions?.apply(this, arguments);
options.push({
content: "跳转uinodes.com查看文档",
callback: async () => {
const url = buildNodeUrl(nodeData.python_module, nodeData.name);
if (url) {
const confirmed = confirm(`是否要打开以下链接?\n\n${url}\n\n点击"确定"将在新标签页中打开此链接。`);
if (confirmed) {
window.open(url, '_blank');
}
}
}
})
}
}