Files
Alexopus edce4685f0 Restrict textarea enhancement to ComfyUI dom-widgets
Excludes textareas outside .dom-widget (e.g. settings/form inputs)
and markdown widgets, instead of blocklisting specific containers.
2026-09-08 21:55:08 +02:00

205 lines
6.5 KiB
JavaScript

import { html } from "../highlighting/html.js";
import { SyntaxTokenizer } from "../highlighting/tokenizer.js";
import { SyntaxHighlighter } from "../highlighting/highlighter.js";
import { config } from "./state.js";
import {
originalTextareaBackgroundColors,
refreshAuthoredBackgroundColor,
setOverlayPosition,
setOverlayStyle,
} from "./styleUtils.js";
import { addTooltips } from "./tooltipManager.js";
import { api } from "../../../../scripts/api.js";
const enhancedTextareas = new WeakSet();
export function syncText(inputEl, overlayEl, tries = 1) {
const text = inputEl.value;
overlayEl.textContent = text;
const { colors, errorColor, wildcardColor, highlightType } = config;
const loraColor = colors ? colors[0] : undefined;
if (
!colors ||
!errorColor ||
!loraColor ||
!wildcardColor ||
highlightType === undefined
) {
if (tries < 5) {
setTimeout(() => syncText(inputEl, overlayEl, tries++), tries * 5);
}
return;
}
const tokenizer = new SyntaxTokenizer();
const highlighter = new SyntaxHighlighter();
const tokens = tokenizer.tokenize(text);
let highlightedText = highlighter.processTokens(tokens);
const processNode = (node) => {
if (node.nodeType === Node.TEXT_NODE) {
return node.textContent
.split(",")
.map((tag) => {
if (!tag.trim()) return tag;
return html("span", tag, {
className: "tag-span",
style: { height: "fit-content" },
attributes: { "data-tag": tag.trim() },
});
})
.join(",");
} else if (node.nodeType === Node.ELEMENT_NODE) {
const textContent = node.textContent;
if (textContent.includes(",")) {
const tags = textContent.split(",");
let result = node.outerHTML;
tags.forEach((tag, index) => {
if (tag.trim()) {
const tagRegex = new RegExp(
`(${tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`,
"g",
);
result = result.replace(
tagRegex,
html("span", "$1", {
className: "tag-span",
style: { height: "fit-content" },
attributes: { "data-tag": tag.trim() },
escape: false,
}),
);
}
});
return result;
} else {
const clone = node.cloneNode(true);
const trimmedText = textContent.trim();
if (trimmedText) {
return html("span", clone.outerHTML, {
className: "tag-span",
style: { height: "fit-content" },
attributes: { "data-tag": trimmedText },
escape: false,
});
}
return clone.outerHTML;
}
}
return "";
};
const tempDiv = document.createElement("div");
tempDiv.innerHTML = highlightedText;
let result = Array.from(tempDiv.childNodes)
.map((node) => processNode(node))
.join("");
overlayEl.innerHTML = result;
}
function setTextColors(inputEl, overlayEl) {
syncText(inputEl, overlayEl);
}
export function enhanceTextarea(textarea) {
if (
enhancedTextareas.has(textarea) ||
!textarea.closest(".dom-widget") ||
textarea.closest(".widget-markdown")
) {
return;
}
enhancedTextareas.add(textarea);
const overlayEl = document.createElement("div");
overlayEl.className = "input-overlay";
textarea.parentNode.insertBefore(overlayEl, textarea);
originalTextareaBackgroundColors.set(textarea, {
value: textarea.style.getPropertyValue("background-color"),
backgroundColorPriority:
textarea.style.getPropertyPriority("background-color"),
});
refreshAuthoredBackgroundColor(textarea);
textarea.style.backgroundColor = "transparent";
textarea.style.position = "relative";
textarea.style.zIndex = "1";
setOverlayPosition(textarea, overlayEl);
setOverlayStyle(textarea, overlayEl);
setTextColors(textarea, overlayEl);
addTooltips(textarea);
textarea.addEventListener("scroll", () => {
overlayEl.scrollTop = textarea.scrollTop;
overlayEl.scrollLeft = textarea.scrollLeft;
});
textarea.addEventListener("input", () => {
syncText(textarea, overlayEl);
setOverlayStyle(textarea, overlayEl);
});
textarea.addEventListener("paste", () => {
setTimeout(() => {
try {
if (overlayEl && document.contains(overlayEl)) {
syncText(textarea, overlayEl);
setOverlayStyle(textarea, overlayEl);
} else {
console.error(
"Overlay element not found during paste operation",
);
}
} catch (err) {
console.error("Error handling paste event:", err);
}
}, 10);
});
api.addEventListener("update_text_highlight", (event) => {
setTextColors(textarea, overlayEl);
setOverlayStyle(textarea, overlayEl);
syncText(textarea, overlayEl);
});
textarea.addEventListener("keydown", (event) => {
if (
event.ctrlKey &&
(event.key === "ArrowUp" || event.key === "ArrowDown")
) {
setTimeout(() => {
syncText(textarea, overlayEl);
}, 10);
}
});
const observer = new MutationObserver(() => {
refreshAuthoredBackgroundColor(textarea);
setOverlayPosition(textarea, overlayEl);
setOverlayStyle(textarea, overlayEl);
});
observer.observe(textarea, {
attributes: true,
attributeFilter: ["style"],
childList: true,
subtree: true,
characterData: true,
});
const parentObserver = new MutationObserver(() => {
if (!document.contains(textarea)) {
overlayEl.remove();
}
});
parentObserver.observe(textarea.parentNode, {
childList: true,
});
}