Files
ShammiG-ComfyUI-Simple_Read…/web/Simple_Readable_Metadata_Text_Viewer_SG.js
T

1434 lines
65 KiB
JavaScript

import { app } from "../../scripts/app.js";
app.registerExtension({
name: "SimpleReadableMetadataTextViewerSG",
async beforeRegisterNodeDef(nodeType, nodeData, appRef) {
if (nodeData?.name !== "Simple Readable Metadata Text Viewer-SG") return;
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
origOnNodeCreated?.apply(this, arguments);
// ============ COUNTER ROW (Top) ============
const counterContainer = document.createElement("div");
counterContainer.style.position = "absolute";
counterContainer.style.top = "0";
counterContainer.style.left = "0";
counterContainer.style.right = "0";
counterContainer.style.display = "flex";
counterContainer.style.justifyContent = "flex-start";
counterContainer.style.alignItems = "center";
counterContainer.style.gap = "10px";
counterContainer.style.padding = "2px 5px";
counterContainer.style.fontSize = "10px";
counterContainer.style.fontFamily = "monospace";
counterContainer.style.height = "20px";
counterContainer.style.backgroundColor = "rgba(0, 0, 0, 0.3)";
counterContainer.style.zIndex = "10";
counterContainer.style.pointerEvents = "none";
counterContainer.style.overflow = "hidden";
counterContainer.style.minWidth = "0";
// Counter display
const counter = document.createElement("div");
counter.style.color = "#999";
counter.style.whiteSpace = "nowrap";
counter.style.flex = "0 0 auto";
counter.textContent = "0 chars | 0 words | 0 lines";
counterContainer.appendChild(counter);
// ============ BUTTON BAR ROW (Below Counter) ============
const buttonBarContainer = document.createElement("div");
buttonBarContainer.style.position = "absolute";
buttonBarContainer.style.top = "22px";
buttonBarContainer.style.left = "0";
buttonBarContainer.style.right = "0";
buttonBarContainer.style.display = "flex";
buttonBarContainer.style.gap = "3px";
buttonBarContainer.style.flexWrap = "nowrap";
buttonBarContainer.style.alignItems = "center";
buttonBarContainer.style.padding = "2px 5px";
buttonBarContainer.style.height = "25px";
buttonBarContainer.style.backgroundColor = "rgba(0, 0, 0, 0.26)";
buttonBarContainer.style.zIndex = "10";
buttonBarContainer.style.pointerEvents = "auto";
buttonBarContainer.style.overflow = "hidden";
buttonBarContainer.style.minWidth = "0";
// Main textarea (declared early for button references)
const box = document.createElement("textarea");
box.readOnly = true;
box.className = "comfy-multiline-input";
box.placeholder = "No text connected...";
box.style.resize = "none";
box.style.boxSizing = "border-box";
box.style.fontFamily = "monospace";
box.style.fontSize = "14px";
box.style.lineHeight = "1.5";
box.style.whiteSpace = "pre-wrap";
box.style.overflowWrap = "break-word";
box.style.width = "100%";
box.style.height = "100%";
box.style.paddingTop = "49px"; // 20 (counter) + 25 (button bar) + 4px buffer
// Highlight overlay div
const highlightDiv = document.createElement("div");
highlightDiv.style.position = "absolute";
highlightDiv.style.top = "0";
highlightDiv.style.left = "0";
highlightDiv.style.width = "100%";
highlightDiv.style.height = "100%";
highlightDiv.style.pointerEvents = "none";
highlightDiv.style.fontFamily = "monospace";
highlightDiv.style.fontSize = "14px";
highlightDiv.style.lineHeight = "1.5";
highlightDiv.style.whiteSpace = "pre-wrap";
highlightDiv.style.padding = "0";
highlightDiv.style.paddingTop = "49px";
highlightDiv.style.boxSizing = "border-box";
highlightDiv.style.color = "transparent";
highlightDiv.style.overflow = "hidden";
highlightDiv.style.zIndex = "5";
highlightDiv.style.display = "none";
// ============ BUTTONS ============
// Toggle Theme button
const themeBtn = document.createElement("button");
themeBtn.textContent = "🌙";
themeBtn.title = "Toggle Theme (Dark/Light)";
themeBtn.style.fontSize = "12px";
themeBtn.style.padding = "1px 4px";
themeBtn.style.cursor = "pointer";
themeBtn.style.minWidth = "24px";
themeBtn.style.width = "24px";
themeBtn.style.flexShrink = "0";
themeBtn.style.flexBasis = "24px";
this.properties = this.properties || {};
this.properties.theme = this.properties.theme || "Dark";
themeBtn.onclick = () => {
this.properties.theme = this.properties.theme === "Dark" ? "Light" : "Dark";
themeBtn.textContent = this.properties.theme === "Dark" ? "🌙" : "☀️";
this._applyTheme(this.properties.theme);
};
// Copy button
const copyBtn = document.createElement("button");
copyBtn.textContent = "📋";
copyBtn.title = "Copy to clipboard";
copyBtn.style.fontSize = "12px";
copyBtn.style.padding = "1px 4px";
copyBtn.style.cursor = "pointer";
copyBtn.style.minWidth = "24px";
copyBtn.style.width = "24px";
copyBtn.style.flexShrink = "0";
copyBtn.style.flexBasis = "24px";
copyBtn.onclick = () => {
const start = box.selectionStart;
const end = box.selectionEnd;
const textToCopy = (start !== end) ? box.value.substring(start, end) : box.value;
navigator.clipboard.writeText(textToCopy);
copyBtn.textContent = "✓";
setTimeout(() => {
copyBtn.textContent = "📋";
}, 1500);
};
// Text Wrap toggle button
const wrapBtn = document.createElement("button");
wrapBtn.textContent = "↔️";
wrapBtn.title = "Toggle Text Wrap";
wrapBtn.style.fontSize = "12px";
wrapBtn.style.padding = "1px 4px";
wrapBtn.style.cursor = "pointer";
wrapBtn.style.minWidth = "24px";
wrapBtn.style.width = "24px";
wrapBtn.style.flexShrink = "0";
wrapBtn.style.flexBasis = "24px";
this.properties.word_wrap = this.properties.word_wrap ?? true;
wrapBtn.style.opacity = this.properties.word_wrap ? "1" : "0.5";
wrapBtn.onclick = () => {
this.properties.word_wrap = !this.properties.word_wrap;
wrapBtn.style.opacity = this.properties.word_wrap ? "1" : "0.5";
box.style.whiteSpace = this.properties.word_wrap ? "pre-wrap" : "pre";
box.style.overflowWrap = this.properties.word_wrap ? "break-word" : "normal";
highlightDiv.style.whiteSpace = this.properties.word_wrap ? "pre-wrap" : "pre";
highlightDiv.style.overflowWrap = this.properties.word_wrap ? "break-word" : "normal";
};
// Text Highlight toggle button
const highlightBtn = document.createElement("button");
highlightBtn.textContent = "🔍";
highlightBtn.title = "Search / Highlight Text";
highlightBtn.style.fontSize = "12px";
highlightBtn.style.padding = "1px 4px";
highlightBtn.style.cursor = "pointer";
highlightBtn.style.minWidth = "24px";
highlightBtn.style.width = "24px";
highlightBtn.style.flexShrink = "0";
highlightBtn.style.flexBasis = "24px";
this.properties.text_filter = this.properties.text_filter ?? false;
highlightBtn.style.opacity = this.properties.text_filter ? "1" : "0.5";
// Text Filter button
const filterBtn = document.createElement("button");
filterBtn.textContent = "🎯";
filterBtn.title = "Filter / Show Only Matching Lines";
filterBtn.style.fontSize = "12px";
filterBtn.style.padding = "1px 4px";
filterBtn.style.cursor = "pointer";
filterBtn.style.minWidth = "24px";
filterBtn.style.width = "24px";
filterBtn.style.flexShrink = "0";
filterBtn.style.flexBasis = "24px";
this.properties.line_filter = this.properties.line_filter ?? false;
filterBtn.style.opacity = this.properties.line_filter ? "1" : "0.5";
// Export button
const exportBtn = document.createElement("button");
exportBtn.textContent = "💾";
exportBtn.title = "Export to file";
exportBtn.style.fontSize = "12px";
exportBtn.style.padding = "1px 4px";
exportBtn.style.cursor = "pointer";
exportBtn.style.minWidth = "24px";
exportBtn.style.width = "24px";
exportBtn.style.flexShrink = "0";
exportBtn.style.flexBasis = "24px";
exportBtn.onclick = () => {
const now = new Date();
const day = String(now.getDate()).padStart(2, '0');
const month = String(now.getMonth() + 1).padStart(2, '0');
const year = now.getFullYear();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const dateTime = `${day}-${month}-${year}_${hours}_${minutes}_${seconds}`;
const blob = new Blob([box.value], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `comfy_${dateTime}.txt`;
a.click();
URL.revokeObjectURL(url);
};
// ============ PRETTY JSON TOGGLE BUTTON ============
let prettyJsonBtn = document.createElement("button");
prettyJsonBtn.textContent = "{}";
prettyJsonBtn.title = "Toggle Pretty JSON Format";
prettyJsonBtn.style.fontSize = "12px";
prettyJsonBtn.style.padding = "1px 4px";
prettyJsonBtn.style.cursor = "pointer";
prettyJsonBtn.style.minWidth = "24px";
prettyJsonBtn.style.width = "24px";
prettyJsonBtn.style.flexShrink = "0";
prettyJsonBtn.style.flexBasis = "24px";
this.properties.pretty_json_mode = this.properties.pretty_json_mode ?? false;
prettyJsonBtn.style.opacity = this.properties.pretty_json_mode ? "1" : "0.5";
prettyJsonBtn.onclick = () => {
this.properties.pretty_json_mode = !this.properties.pretty_json_mode;
prettyJsonBtn.style.opacity = this.properties.pretty_json_mode ? "1" : "0.5";
if (this.properties.pretty_json_mode) {
try {
const textToParse = this._tv_original_text.trim();
if (!textToParse) {
this.properties.pretty_json_mode = false;
prettyJsonBtn.style.opacity = "0.5";
return;
}
let jsonObj;
try {
jsonObj = JSON.parse(textToParse);
} catch (parseError) {
try {
jsonObj = eval('(' + textToParse + ')');
} catch (evalError) {
this.properties.pretty_json_mode = false;
prettyJsonBtn.style.opacity = "0.5";
return;
}
}
const prettyJson = JSON.stringify(jsonObj, null, 2);
this._tv_pretty_json_text = prettyJson;
this.properties.pretty_json_text = prettyJson;
box.value = prettyJson;
this._updateCounter();
} catch (err) {
this.properties.pretty_json_mode = false;
prettyJsonBtn.style.opacity = "0.5";
return;
}
} else {
box.value = this._tv_original_text;
this._updateCounter();
if (this.properties.text_filter) {
this._applyFilter();
}
if (this.properties.line_filter) {
this._applyLineFilter();
}
}
};
// Select All button
const selectAllBtn = document.createElement("button");
selectAllBtn.textContent = "☑️";
selectAllBtn.title = "Select All Text";
selectAllBtn.style.fontSize = "12px";
selectAllBtn.style.padding = "1px 4px";
selectAllBtn.style.cursor = "pointer";
selectAllBtn.style.minWidth = "24px";
selectAllBtn.style.width = "24px";
selectAllBtn.style.flexShrink = "0";
selectAllBtn.onclick = () => {
box.select();
box.focus();
selectAllBtn.textContent = "✓";
setTimeout(() => {
selectAllBtn.textContent = "☑️";
}, 1500);
};
// Paste button
const pasteBtn = document.createElement("button");
pasteBtn.textContent = "📄";
pasteBtn.title = "Paste from clipboard";
pasteBtn.style.fontSize = "12px";
pasteBtn.style.padding = "1px 4px";
pasteBtn.style.cursor = "pointer";
pasteBtn.style.minWidth = "24px";
pasteBtn.style.width = "24px";
pasteBtn.style.flexShrink = "0";
pasteBtn.onclick = async () => {
try {
const clipboardText = await navigator.clipboard.readText();
const start = box.selectionStart;
const end = box.selectionEnd;
const currentValue = box.value;
const newValue = currentValue.substring(0, start) + clipboardText + currentValue.substring(end);
box.value = newValue;
const newCursorPos = start + clipboardText.length;
box.setSelectionRange(newCursorPos, newCursorPos);
box.focus();
this._tv_original_text = newValue;
this._tv_pretty_json_text = "";
this.properties.text = newValue;
this.properties.pretty_json_text = "";
const textWidget = this.widgets?.find(w => w.name === "text");
if (textWidget) {
textWidget.value = newValue;
}
this._updateCounter();
if (this._tv_auto_resize !== false) {
requestAnimationFrame(() => {
const contentSize = this._calculateContentSize();
if (contentSize) {
this.size = contentSize;
this.onResize(this.size);
if (this.graph && this.graph.canvas) {
this.graph.canvas.setDirty(true, true);
}
}
});
}
pasteBtn.textContent = "✓";
setTimeout(() => {
pasteBtn.textContent = "📄";
}, 1500);
} catch (err) {
console.error('Failed to read clipboard:', err);
pasteBtn.textContent = "✗";
setTimeout(() => {
pasteBtn.textContent = "📄";
}, 1500);
}
};
// Delete All button
const deleteAllBtn = document.createElement("button");
deleteAllBtn.textContent = "🗑️";
deleteAllBtn.title = "Delete All Text";
deleteAllBtn.style.fontSize = "12px";
deleteAllBtn.style.padding = "1px 4px";
deleteAllBtn.style.cursor = "pointer";
deleteAllBtn.style.minWidth = "24px";
deleteAllBtn.style.width = "24px";
deleteAllBtn.style.flexShrink = "0";
deleteAllBtn.style.flexBasis = "24px";
deleteAllBtn.style.opacity = "1";
// === DELETE CONFIRMATION CONTROLS (Inline) ===
const deleteConfirmControls = document.createElement("div");
deleteConfirmControls.style.position = "absolute";
deleteConfirmControls.style.top = "49px";
deleteConfirmControls.style.left = "0";
deleteConfirmControls.style.right = "0";
deleteConfirmControls.style.display = "none";
deleteConfirmControls.style.flexDirection = "column";
deleteConfirmControls.style.gap = "10px";
deleteConfirmControls.style.padding = "12px";
deleteConfirmControls.style.backgroundColor = "rgba(0, 0, 0, 0.9)";
deleteConfirmControls.style.border = "3px solid #ff6b6b";
deleteConfirmControls.style.borderRadius = "4px";
deleteConfirmControls.style.boxShadow = "0 2px 8px rgba(0, 0, 0, 0.5)";
deleteConfirmControls.style.zIndex = "15";
deleteConfirmControls.style.backdropFilter = "blur(4px)";
// Warning message
const deleteMessage = document.createElement("div");
deleteMessage.textContent = "⚠️ Delete all text? This cannot be undone.";
deleteMessage.style.fontSize = "13px";
deleteMessage.style.color = "#ff6b6b";
deleteMessage.style.fontWeight = "bold";
deleteMessage.style.textAlign = "center";
deleteMessage.style.fontFamily = "monospace";
// Button container
const deleteButtonContainer = document.createElement("div");
deleteButtonContainer.style.display = "flex";
deleteButtonContainer.style.gap = "10px";
deleteButtonContainer.style.justifyContent = "center";
// Confirm Delete button
const confirmDeleteBtn = document.createElement("button");
confirmDeleteBtn.textContent = "Delete All";
confirmDeleteBtn.style.fontSize = "12px";
confirmDeleteBtn.style.padding = "6px 16px";
confirmDeleteBtn.style.cursor = "pointer";
confirmDeleteBtn.style.border = "2px solid #ff6b6b";
confirmDeleteBtn.style.borderRadius = "3px";
confirmDeleteBtn.style.backgroundColor = "#ff6b6b";
confirmDeleteBtn.style.color = "#fff";
confirmDeleteBtn.style.fontWeight = "bold";
// Cancel button
const cancelDeleteBtn = document.createElement("button");
cancelDeleteBtn.textContent = "Cancel";
cancelDeleteBtn.style.fontSize = "12px";
cancelDeleteBtn.style.padding = "6px 16px";
cancelDeleteBtn.style.cursor = "pointer";
cancelDeleteBtn.style.border = "2px solid #555";
cancelDeleteBtn.style.borderRadius = "3px";
cancelDeleteBtn.style.backgroundColor = "#333";
cancelDeleteBtn.style.color = "#fff";
cancelDeleteBtn.style.fontWeight = "bold";
// Hover effects
confirmDeleteBtn.onmouseenter = () => confirmDeleteBtn.style.backgroundColor = "#ff5252";
confirmDeleteBtn.onmouseleave = () => confirmDeleteBtn.style.backgroundColor = "#ff6b6b";
cancelDeleteBtn.onmouseenter = () => cancelDeleteBtn.style.backgroundColor = "#666";
cancelDeleteBtn.onmouseleave = () => cancelDeleteBtn.style.backgroundColor = "#333";
// Assemble controls
deleteButtonContainer.appendChild(confirmDeleteBtn);
deleteButtonContainer.appendChild(cancelDeleteBtn);
deleteConfirmControls.appendChild(deleteMessage);
deleteConfirmControls.appendChild(deleteButtonContainer);
// Delete execution function
const executeDelete = () => {
box.value = "";
if (!this.properties) this.properties = {};
this._tv_original_text = "";
this._tv_pretty_json_text = "";
this.properties.text = "";
this.properties.pretty_json_text = "";
const textWidget = this.widgets?.find(w => w.name === "text");
if (textWidget) textWidget.value = "";
this._updateCounter();
// Clear filters
if (this._tv_filter_input) this._tv_filter_input.value = "";
if (this._tv_line_filter_input) this._tv_line_filter_input.value = "";
// Reset modes
this.properties.pretty_json_mode = false;
if (prettyJsonBtn) prettyJsonBtn.style.opacity = "0.5";
// Clear highlights
if (this._tv_highlight_div) this._tv_highlight_div.innerHTML = '';
this._tv_match_positions = [];
if (matchCounter) matchCounter.textContent = "0 / 0";
if (lineMatchCounter) lineMatchCounter.textContent = "0 lines";
// Close dialog
deleteConfirmControls.style.display = "none";
deleteAllBtn.style.opacity = "1";
this._updateTextPadding();
// Success feedback
deleteAllBtn.textContent = "✓";
setTimeout(() => { deleteAllBtn.textContent = "🗑️"; }, 1500);
};
// Cancel function
const cancelDelete = () => {
deleteConfirmControls.style.display = "none";
deleteAllBtn.style.opacity = "1";
this._updateTextPadding();
};
// Button handlers
confirmDeleteBtn.onclick = executeDelete;
cancelDeleteBtn.onclick = cancelDelete;
// Main Delete Button Click (Toggle)
deleteAllBtn.onclick = () => {
const isVisible = deleteConfirmControls.style.display === "flex";
// Close other controls (like Font Size) if we are opening Delete
if (!isVisible) {
if (typeof textSizeControls !== 'undefined') textSizeControls.style.display = 'none';
if (typeof textSizeBtn !== 'undefined') textSizeBtn.style.opacity = '1';
if (highlightControls) highlightControls.style.display = 'none';
if (filterControls) filterControls.style.display = 'none';
}
deleteConfirmControls.style.display = isVisible ? "none" : "flex";
deleteAllBtn.style.opacity = isVisible ? "1" : "0.5";
this._updateTextPadding();
};
// ============ TEXT SIZE BUTTON ============
const textSizeBtn = document.createElement("button");
textSizeBtn.textContent = "Aa";
textSizeBtn.title = "Text Size (6-72)";
textSizeBtn.style.fontSize = "12px";
textSizeBtn.style.padding = "1px 4px";
textSizeBtn.style.cursor = "pointer";
textSizeBtn.style.minWidth = "24px";
textSizeBtn.style.width = "24px";
textSizeBtn.style.flexShrink = "0";
textSizeBtn.style.fontWeight = "bold";
textSizeBtn.style.flexBasis = "24px";
textSizeBtn.style.opacity = "1";
// Text size controls (inline popup)
const textSizeControls = document.createElement("div");
textSizeControls.style.position = "absolute";
textSizeControls.style.top = "49px";
textSizeControls.style.left = "0";
textSizeControls.style.right = "0";
textSizeControls.style.display = "none";
textSizeControls.style.flexDirection = "row";
textSizeControls.style.gap = "5px";
textSizeControls.style.alignItems = "center";
textSizeControls.style.padding = "8px";
textSizeControls.style.backgroundColor = "rgba(0, 0, 0, 0.85)";
textSizeControls.style.border = "3px solid #4CAF50";
textSizeControls.style.borderRadius = "4px";
textSizeControls.style.boxShadow = "0 2px 8px rgba(0, 0, 0, 0.5)";
textSizeControls.style.zIndex = "15";
textSizeControls.style.backdropFilter = "blur(4px)";
// Label
const textSizeLabel = document.createElement("span");
textSizeLabel.textContent = "Text Size:";
textSizeLabel.style.fontSize = "13px";
textSizeLabel.style.color = "#4CAF50";
textSizeLabel.style.fontWeight = "bold";
textSizeLabel.style.fontFamily = "monospace";
// Input field
const textSizeInput = document.createElement("input");
textSizeInput.type = "number";
textSizeInput.min = "6";
textSizeInput.max = "72";
textSizeInput.value = this.properties.font_size ?? 14;
textSizeInput.style.width = "60px";
textSizeInput.style.height = "28px";
textSizeInput.style.boxSizing = "border-box";
textSizeInput.style.fontFamily = "monospace";
textSizeInput.style.fontSize = "12px";
textSizeInput.style.padding = "4px";
textSizeInput.style.border = "2px solid #555";
textSizeInput.style.borderRadius = "3px";
textSizeInput.style.backgroundColor = "#1e1e1e";
textSizeInput.style.color = "#d4d4d4";
// Apply button
const applyBtn = document.createElement("button");
applyBtn.textContent = "Apply";
applyBtn.style.fontSize = "12px";
applyBtn.style.padding = "4px 12px";
applyBtn.style.cursor = "pointer";
applyBtn.style.border = "2px solid #4CAF50";
applyBtn.style.borderRadius = "3px";
applyBtn.style.backgroundColor = "#4CAF50";
applyBtn.style.color = "#fff";
applyBtn.style.fontWeight = "bold";
// Close button
const closeBtn = document.createElement("button");
closeBtn.textContent = "✕";
closeBtn.style.fontSize = "14px";
closeBtn.style.padding = "4px 8px";
closeBtn.style.cursor = "pointer";
closeBtn.style.border = "2px solid #555";
closeBtn.style.borderRadius = "3px";
closeBtn.style.backgroundColor = "#333";
closeBtn.style.color = "#fff";
closeBtn.style.fontWeight = "bold";
// Assemble controls
textSizeControls.appendChild(textSizeLabel);
textSizeControls.appendChild(textSizeInput);
textSizeControls.appendChild(applyBtn);
textSizeControls.appendChild(closeBtn);
// Apply size function
const applySize = () => {
const sizeNum = parseInt(textSizeInput.value);
if (!isNaN(sizeNum) && sizeNum >= 6 && sizeNum <= 72) {
this.properties.font_size = sizeNum;
box.style.fontSize = `${sizeNum}px`;
if (highlightDiv) highlightDiv.style.fontSize = `${sizeNum}px`;
textSizeControls.style.display = 'none';
textSizeBtn.style.opacity = '1';
this._updateTextPadding();
} else {
textSizeInput.style.borderColor = '#f44336';
setTimeout(() => textSizeInput.style.borderColor = '#555', 1000);
}
};
// Close function
const closeTextSize = () => {
textSizeControls.style.display = "none";
textSizeBtn.style.opacity = "1";
this._updateTextPadding();
};
// Button click handlers
applyBtn.onclick = applySize;
closeBtn.onclick = closeTextSize;
// Enter to apply, Escape to close
textSizeInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
applySize();
} else if (e.key === "Escape") {
e.preventDefault();
closeTextSize();
}
});
// Toggle text size controls
textSizeBtn.onclick = () => {
const isVisible = textSizeControls.style.display === "flex";
textSizeControls.style.display = isVisible ? "none" : "flex";
textSizeBtn.style.opacity = isVisible ? "1" : "0.5";
if (!isVisible) {
textSizeInput.value = this.properties.font_size || 14;
setTimeout(() => textSizeInput.focus(), 100);
// Force update padding to show controls
this._updateTextPadding();
} else {
this._updateTextPadding();
}
};
// Copy Positive Prompt button
const copyPosBtn = document.createElement("button");
copyPosBtn.textContent = "Copy Positive";
copyPosBtn.title = "Extract & Copy Positive Prompt";
copyPosBtn.style.fontSize = "10px";
copyPosBtn.style.padding = "1px 2px";
copyPosBtn.style.cursor = "pointer";
copyPosBtn.style.minWidth = "50px";
copyPosBtn.style.width = "50px";
copyPosBtn.style.flexShrink = "0";
copyPosBtn.style.flexBasis = "24px";
copyPosBtn.onclick = () => {
const text = box.value;
let positivePrompt = "";
// METHOD 1: Line-by-line state machine parser (Safest)
const lines = text.split('\n');
let capturing = false;
let buffer = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Start capturing when we see "Positive:"
if (line.trim().startsWith("Positive:")) {
capturing = true;
// Remove "Positive:" prefix from the first line
let content = line.substring(line.indexOf("Positive:") + 9).trim();
if (content) buffer.push(content);
continue;
}
if (capturing) {
// STOP capturing conditions:
// 1. Line starts with "Negative:"
// 2. Line starts with an Emoji (next section)
// 3. Line matches a Section Header style (=== header ===)
// 4. Empty line (often separates prompts from next section)
const trimmed = line.trim();
if (trimmed.startsWith("Negative:") ||
trimmed.startsWith("Negative prompt:") ||
/^(===|[\u{1F300}-\u{1F9FF}]|Step|Size|Model)/u.test(trimmed)) {
break;
}
// If it's just an empty line, it MIGHT be the end, or just a paragraph break.
// But usually ComfyUI prompts are dense blocks.
// Let's assume a double-newline (empty line) ends the prompt section.
if (trimmed === "") {
break;
}
buffer.push(line);
}
}
if (buffer.length > 0) {
positivePrompt = buffer.join("\n").trim();
}
// METHOD 2: Fallback for A1111 raw format (where prompt is at the very top)
else if (text.indexOf("Steps:") !== -1 || text.indexOf("Negative prompt:") !== -1) {
const negIndex = text.indexOf("Negative prompt:");
const stepsIndex = text.indexOf("Steps:");
let cutIndex = text.length;
if (negIndex !== -1) cutIndex = Math.min(cutIndex, negIndex);
if (stepsIndex !== -1) cutIndex = Math.min(cutIndex, stepsIndex);
if (cutIndex < text.length) {
positivePrompt = text.substring(0, cutIndex).trim();
}
}
// Clean up
if (positivePrompt === "(empty)") positivePrompt = "";
if (positivePrompt) {
navigator.clipboard.writeText(positivePrompt);
copyPosBtn.textContent = "✓";
setTimeout(() => { copyPosBtn.textContent = "+P"; }, 1500);
} else {
copyPosBtn.textContent = "✗";
setTimeout(() => { copyPosBtn.textContent = "+P"; }, 1500);
}
};
// Append buttons to button bar in order
buttonBarContainer.appendChild(deleteAllBtn);
buttonBarContainer.appendChild(copyBtn);
buttonBarContainer.appendChild(selectAllBtn);
buttonBarContainer.appendChild(pasteBtn);
buttonBarContainer.appendChild(highlightBtn);
buttonBarContainer.appendChild(filterBtn);
buttonBarContainer.appendChild(exportBtn);
buttonBarContainer.appendChild(prettyJsonBtn);
buttonBarContainer.appendChild(themeBtn);
buttonBarContainer.appendChild(wrapBtn);
buttonBarContainer.appendChild(textSizeBtn);
buttonBarContainer.appendChild(copyPosBtn);
// Wrapper for textarea and overlays
const textareaWrapper = document.createElement("div");
textareaWrapper.style.position = "relative";
textareaWrapper.style.width = "100%";
textareaWrapper.style.height = "100%";
textareaWrapper.style.paddingBottom = "2px";
textareaWrapper.style.boxSizing = "border-box";
textareaWrapper.appendChild(box);
textareaWrapper.appendChild(highlightDiv);
textareaWrapper.appendChild(counterContainer);
textareaWrapper.appendChild(buttonBarContainer);
textareaWrapper.appendChild(textSizeControls);
textareaWrapper.appendChild(deleteConfirmControls);
// ============ HIGHLIGHT CONTROLS ============
const highlightControls = document.createElement("div");
highlightControls.style.position = "absolute";
highlightControls.style.top = "49px";
highlightControls.style.left = "0";
highlightControls.style.right = "0";
highlightControls.style.display = "none";
highlightControls.style.flexDirection = "row";
highlightControls.style.gap = "5px";
highlightControls.style.alignItems = "center";
highlightControls.style.padding = "8px";
highlightControls.style.backgroundColor = "rgba(0, 0, 0, 0.85)";
highlightControls.style.border = "3px solid #FFD700";
highlightControls.style.borderRadius = "4px";
highlightControls.style.boxShadow = "0 2px 8px rgba(0, 0, 0, 0.5)";
highlightControls.style.zIndex = "15";
highlightControls.style.backdropFilter = "blur(4px)";
highlightControls.style.height = "32px";
// Filter input
const filterInput = document.createElement("input");
filterInput.type = "text";
filterInput.placeholder = "Highlight text (regex supported)...";
filterInput.style.width = "200px";
filterInput.style.minWidth = "200px";
filterInput.style.maxWidth = "600px";
filterInput.style.height = "28px";
filterInput.style.boxSizing = "border-box";
filterInput.style.fontFamily = "monospace";
filterInput.style.fontSize = "12px";
filterInput.style.padding = "4px";
filterInput.style.border = "2px solid #555";
filterInput.style.borderRadius = "3px";
filterInput.addEventListener('input', function() {
const charWidth = 8;
const padding = 50;
const minWidth = 200;
const maxWidth = 600;
const calculatedWidth = Math.min(maxWidth, Math.max(minWidth, this.value.length * charWidth + padding));
this.style.width = calculatedWidth + 'px';
});
// Match counter display
const matchCounter = document.createElement("span");
matchCounter.style.fontSize = "13px";
matchCounter.style.fontWeight = "bold";
matchCounter.style.color = "#FFD700";
matchCounter.style.minWidth = "60px";
matchCounter.style.fontFamily = "monospace";
matchCounter.style.textShadow = "0 0 2px rgba(0, 0, 0, 0.8)";
matchCounter.textContent = "0 / 0";
// Previous match button
const prevBtn = document.createElement("button");
prevBtn.textContent = "↑ Prev";
prevBtn.title = "Previous match (Shift+Enter)";
prevBtn.style.fontSize = "12px";
prevBtn.style.padding = "4px 8px";
prevBtn.style.cursor = "pointer";
prevBtn.style.border = "2px solid #555";
prevBtn.style.borderRadius = "3px";
prevBtn.style.backgroundColor = "#333";
prevBtn.style.color = "#fff";
prevBtn.style.fontWeight = "bold";
// Next match button
const nextBtn = document.createElement("button");
nextBtn.textContent = "Next ↓";
nextBtn.title = "Next match (Enter)";
nextBtn.style.fontSize = "12px";
nextBtn.style.padding = "4px 8px";
nextBtn.style.cursor = "pointer";
nextBtn.style.border = "2px solid #555";
nextBtn.style.borderRadius = "3px";
nextBtn.style.backgroundColor = "#333";
nextBtn.style.color = "#fff";
nextBtn.style.fontWeight = "bold";
highlightControls.appendChild(filterInput);
highlightControls.appendChild(matchCounter);
highlightControls.appendChild(prevBtn);
highlightControls.appendChild(nextBtn);
textareaWrapper.appendChild(highlightControls);
// ============ FILTER CONTROLS ============
const filterControls = document.createElement("div");
filterControls.style.position = "absolute";
filterControls.style.top = "49px";
filterControls.style.left = "0";
filterControls.style.right = "0";
filterControls.style.display = "none";
filterControls.style.flexDirection = "row";
filterControls.style.gap = "5px";
filterControls.style.alignItems = "center";
filterControls.style.padding = "8px";
filterControls.style.backgroundColor = "rgba(0, 0, 0, 0.85)";
filterControls.style.border = "3px solid #4CAF50";
filterControls.style.borderRadius = "4px";
filterControls.style.boxShadow = "0 2px 8px rgba(0, 0, 0, 0.5)";
filterControls.style.zIndex = "15";
filterControls.style.backdropFilter = "blur(4px)";
filterControls.style.height = "32px";
// Filter input for line filtering
const lineFilterInput = document.createElement("input");
lineFilterInput.type = "text";
lineFilterInput.placeholder = "Filter lines (regex supported)...";
lineFilterInput.style.width = "200px";
lineFilterInput.style.minWidth = "200px";
lineFilterInput.style.maxWidth = "600px";
lineFilterInput.style.height = "28px";
lineFilterInput.style.boxSizing = "border-box";
lineFilterInput.style.fontFamily = "monospace";
lineFilterInput.style.fontSize = "12px";
lineFilterInput.style.padding = "4px";
lineFilterInput.style.border = "2px solid #555";
lineFilterInput.style.borderRadius = "3px";
lineFilterInput.addEventListener('input', function() {
const charWidth = 8;
const padding = 50;
const minWidth = 200;
const maxWidth = 600;
const calculatedWidth = Math.min(maxWidth, Math.max(minWidth, this.value.length * charWidth + padding));
this.style.width = calculatedWidth + 'px';
});
// Line match counter
const lineMatchCounter = document.createElement("span");
lineMatchCounter.style.fontSize = "13px";
lineMatchCounter.style.fontWeight = "bold";
lineMatchCounter.style.color = "#4CAF50";
lineMatchCounter.style.minWidth = "80px";
lineMatchCounter.style.fontFamily = "monospace";
lineMatchCounter.style.textShadow = "0 0 2px rgba(0, 0, 0, 0.8)";
lineMatchCounter.textContent = "0 lines";
filterControls.appendChild(lineFilterInput);
filterControls.appendChild(lineMatchCounter);
textareaWrapper.appendChild(filterControls);
// Sync scroll between textarea and highlight div
box.addEventListener('scroll', () => {
highlightDiv.scrollTop = box.scrollTop;
highlightDiv.scrollLeft = box.scrollLeft;
});
// Store references
this._tv_box = box;
this._tv_counter = counter;
this._tv_filter_input = filterInput;
this._tv_highlight_div = highlightDiv;
this._tv_highlight_controls = highlightControls;
this._tv_match_counter = matchCounter;
this._tv_match_positions = [];
this._tv_current_match_index = -1;
this._tv_auto_resize = false;
this._tv_original_text = "";
this._tv_pretty_json_text = "";
this._tv_line_filter_input = lineFilterInput;
this._tv_filter_controls = filterControls;
this._tv_line_match_counter = lineMatchCounter;
this._tv_text_size_controls = textSizeControls;
this._tv_delete_controls = deleteConfirmControls;
// ============ DYNAMIC PADDING MANAGEMENT ============
this._updateTextPadding = () => {
if (!this.properties) return;
// Check visibility of various controls
const hasTextSize = (this._tv_text_size_controls && this._tv_text_size_controls.style.display === "flex");
const hasDelete = (this._tv_delete_controls && this._tv_delete_controls.style.display === "flex");
const hasSearch = this.properties.text_filter;
const hasFilter = this.properties.line_filter;
const basePadding = 49;
const controlsHeight = 32;
let totalPadding = basePadding;
if (hasDelete) {
// Delete dialog is taller than normal controls
totalPadding += 80;
} else if (hasSearch || hasFilter || hasTextSize) {
// Standard single-row controls
totalPadding += controlsHeight;
}
box.style.paddingTop = `${totalPadding}px`;
if (this._tv_highlight_div) this._tv_highlight_div.style.paddingTop = `${totalPadding}px`;
// Ensure all controls are positioned correctly below the header
if (this._tv_highlight_controls) this._tv_highlight_controls.style.top = `${basePadding}px`;
if (this._tv_filter_controls) this._tv_filter_controls.style.top = `${basePadding}px`;
if (this._tv_text_size_controls) this._tv_text_size_controls.style.top = `${basePadding}px`;
if (this._tv_delete_controls) this._tv_delete_controls.style.top = `${basePadding}px`;
};
// Navigation functions for textarea mode
const navigateToMatch = (index) => {
if (this._tv_match_positions.length === 0) return;
this._tv_current_match_index = index;
const pos = this._tv_match_positions[index];
const textBeforeMatch = box.value.substring(0, pos);
const lineNumber = textBeforeMatch.split('\n').length - 1;
const lineHeight = parseFloat(getComputedStyle(box).lineHeight);
const scrollPosition = lineNumber * lineHeight;
box.scrollTop = scrollPosition - (box.clientHeight / 2);
highlightDiv.scrollTop = box.scrollTop;
matchCounter.textContent = `${index + 1} / ${this._tv_match_positions.length}`;
this._applyFilter(index);
};
prevBtn.onclick = () => {
if (this._tv_match_positions.length === 0) return;
const newIndex = (this._tv_current_match_index - 1 + this._tv_match_positions.length) % this._tv_match_positions.length;
navigateToMatch(newIndex);
};
nextBtn.onclick = () => {
if (this._tv_match_positions.length === 0) return;
const newIndex = (this._tv_current_match_index + 1) % this._tv_match_positions.length;
navigateToMatch(newIndex);
};
filterInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
if (e.shiftKey) {
prevBtn.click();
} else {
nextBtn.click();
}
}
});
this._tv_navigate_to_match = navigateToMatch;
// Text Highlight button click handler
highlightBtn.onclick = () => {
this.properties.text_filter = !this.properties.text_filter;
highlightBtn.style.opacity = this.properties.text_filter ? "1" : "0.5";
highlightControls.style.display = this.properties.text_filter ? "flex" : "none";
highlightDiv.style.display = this.properties.text_filter ? "block" : "none";
this._updateTextPadding();
if (!this.properties.text_filter) {
filterInput.value = "";
this._applyFilter();
} else {
setTimeout(() => filterInput.focus(), 100);
}
};
// Text Filter button click handler
filterBtn.onclick = () => {
this.properties.line_filter = !this.properties.line_filter;
filterBtn.style.opacity = this.properties.line_filter ? "1" : "0.5";
filterControls.style.display = this.properties.line_filter ? "flex" : "none";
this._updateTextPadding();
if (!this.properties.line_filter) {
lineFilterInput.value = "";
this._applyLineFilter();
} else {
setTimeout(() => lineFilterInput.focus(), 100);
}
};
// Initialize properties
if (!this.properties) {
this.properties = {};
}
this.properties.font_size = this.properties.font_size ?? 14;
this.properties.theme = this.properties.theme ?? "Dark";
this.properties.word_wrap = this.properties.word_wrap ?? false;
this.properties.text_filter = this.properties.text_filter ?? false;
this.properties.line_filter = this.properties.line_filter ?? false;
this.properties.pretty_json_mode = this.properties.pretty_json_mode ?? false;
// Add DOM widgets
this.addDOMWidget("textDisplay", "customtext", textareaWrapper, {
getValue: () => box.value,
setValue: (v) => {
box.value = v;
}
});
// Filter input handler
filterInput.oninput = () => {
this._tv_current_match_index = -1;
this._applyFilter();
if (this._tv_match_positions.length > 0) {
this._tv_navigate_to_match(0);
}
};
// Line filter input handler
lineFilterInput.oninput = () => {
this._applyLineFilter();
};
// Helper function to set text
this._tv_set = (v) => {
const text = typeof v === "string" ? v : (Array.isArray(v) ? v.join("\n") : "");
this._tv_original_text = text;
this.properties.text = text;
this._tv_pretty_json_text = "";
this.properties.pretty_json_text = "";
this.properties.pretty_json_mode = false;
if (prettyJsonBtn) {
prettyJsonBtn.style.opacity = "0.5";
}
box.value = text;
this._applyFilter();
this._applyLineFilter();
this._updateCounter();
};
// Apply theme
this._applyTheme = (theme) => {
const themes = {
"Dark": {
bg: "#1e1e1e",
color: "#d4d4d4",
border: "#3e3e3e"
},
"Light": {
bg: "#ffffff",
color: "#000000",
border: "#cccccc"
}
};
const t = themes[theme] || themes["Dark"];
box.style.backgroundColor = t.bg;
box.style.color = t.color;
box.style.border = `1px solid ${t.border}`;
filterInput.style.backgroundColor = t.bg;
filterInput.style.color = t.color;
lineFilterInput.style.backgroundColor = t.bg;
lineFilterInput.style.color = t.color;
};
// Apply text highlighting with navigation
this._applyFilter = (currentMatchIndex = -1) => {
const filterValue = filterInput.value;
if (!this.properties.line_filter) {
box.value = this.properties.pretty_json_mode ? this._tv_pretty_json_text : this._tv_original_text;
}
this._updateCounter();
if (!filterValue || !this.properties.text_filter) {
highlightDiv.innerHTML = '';
this._tv_match_positions = [];
matchCounter.textContent = "0 / 0";
return;
}
try {
const regex = new RegExp(filterValue, 'gi');
const text = box.value;
this._tv_match_positions = [];
let match;
while ((match = regex.exec(text)) !== null) {
this._tv_match_positions.push(match.index);
}
if (currentMatchIndex === -1) {
matchCounter.textContent = `0 / ${this._tv_match_positions.length}`;
}
let matchIndex = 0;
const highlightedText = text.replace(new RegExp(filterValue, 'gi'), (match) => {
const isCurrentMatch = matchIndex === currentMatchIndex;
matchIndex++;
if (isCurrentMatch) {
return `<mark style="background-color: #ff6b6b; color: #000;">${match}</mark>`;
} else {
return `<mark style="background-color: #ffeb3b; color: #000;">${match}</mark>`;
}
});
highlightDiv.innerHTML = highlightedText;
highlightDiv.style.fontSize = box.style.fontSize;
highlightDiv.scrollTop = box.scrollTop;
highlightDiv.scrollLeft = box.scrollLeft;
} catch (e) {
console.warn("Invalid regex pattern");
highlightDiv.innerHTML = '';
this._tv_match_positions = [];
matchCounter.textContent = "0 / 0";
}
};
// Apply line filtering
this._applyLineFilter = () => {
const filterValue = this._tv_line_filter_input.value;
if (!filterValue || !this.properties.line_filter) {
box.value = this.properties.pretty_json_mode ? this._tv_pretty_json_text : this._tv_original_text;
this._tv_line_match_counter.textContent = "0 lines";
this._updateCounter();
return;
}
try {
const regex = new RegExp(filterValue, 'i');
const sourceText = this.properties.pretty_json_mode ? this._tv_pretty_json_text : this._tv_original_text;
const lines = sourceText.split('\n');
const matchedLines = lines.filter(line => regex.test(line));
box.value = matchedLines.join('\n');
this._tv_line_match_counter.textContent = `${matchedLines.length} lines`;
this._updateCounter();
if (this.properties.text_filter) {
this._applyFilter();
}
} catch (e) {
console.warn("Invalid regex pattern");
box.value = this.properties.pretty_json_mode ? this._tv_pretty_json_text : this._tv_original_text;
this._tv_line_match_counter.textContent = "0 lines";
this._updateCounter();
}
};
// Update counter
this._updateCounter = () => {
const text = box.value;
const chars = text.length;
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
const lines = text ? text.split('\n').length : 0;
counter.textContent = `${chars} chars | ${words} words | ${lines} lines`;
};
// Initial setup - restore saved text if available
const savedText = this.properties?.text ?? "";
if (savedText) {
this._tv_original_text = savedText;
this._tv_pretty_json_text = this.properties?.pretty_json_text ?? "";
box.value = this.properties.pretty_json_mode ? this._tv_pretty_json_text : savedText;
this._updateCounter();
// Restore filter states if they exist
if (this.properties.text_filter && this._tv_filter_input && this.properties.filter_text) {
this._tv_filter_input.value = this.properties.filter_text;
this._tv_highlight_controls.style.display = "flex";
this._tv_highlight_div.style.display = "block";
this._applyFilter();
}
if (this.properties.line_filter && this._tv_line_filter_input && this.properties.line_filter_text) {
this._tv_line_filter_input.value = this.properties.line_filter_text;
this._tv_filter_controls.style.display = "flex";
this._applyLineFilter();
}
this._updateTextPadding();
} else {
this._tv_set("");
}
this._applyTheme(this.properties.theme);
this.size = [500, 200];
};
// Calculate content-based size
nodeType.prototype._calculateContentSize = function() {
if (!this._tv_box) return null;
const text = this._tv_box.value;
if (!text || text.length === 0) return [500, 200];
const lines = text.split('\n');
const longestLine = Math.max(...lines.map(l => l.length), 1);
const charWidth = 8.5;
const baseLineHeight = 21;
const headerHeight = 35;
const widgetHeight = 40;
const margin = 15;
const textareaPadding = 10;
const maxWidth = 2000;
const maxHeight = 2000;
let contentWidth = (longestLine * charWidth) + (textareaPadding * 2) + (margin * 2);
contentWidth = Math.max(400, Math.min(contentWidth, maxWidth));
const textareaWidth = contentWidth - (margin * 2) - (textareaPadding * 2);
const charsPerLine = Math.floor(textareaWidth / charWidth);
let totalVisualLines = 0;
for (const line of lines) {
if (line.length === 0) {
totalVisualLines += 1;
} else {
totalVisualLines += Math.ceil(line.length / charsPerLine);
}
}
let contentHeight = (totalVisualLines * baseLineHeight) + headerHeight + widgetHeight + margin + (textareaPadding * 2);
contentHeight = Math.max(300, Math.min(contentHeight, maxHeight));
return [contentWidth, contentHeight];
};
// Sync textarea size
nodeType.prototype._syncTextareaSize = function() {
if (!this._tv_box || !this.size) return;
const nodeWidth = this.size[0];
const nodeHeight = this.size[1];
const headerHeight = 30;
const widgetHeight = 15;
const leftMargin = 10;
const rightMargin = 10;
const bottomMargin = 5;
const textareaWidth = nodeWidth - leftMargin - rightMargin;
const textareaHeight = nodeHeight - headerHeight - widgetHeight - bottomMargin;
this._tv_box.style.width = `${Math.max(50, textareaWidth)}px`;
this._tv_box.style.height = `${Math.max(40, textareaHeight)}px`;
if (this._tv_highlight_div) {
this._tv_highlight_div.style.width = `${Math.max(50, textareaWidth)}px`;
this._tv_highlight_div.style.height = `${Math.max(40, textareaHeight)}px`;
}
};
// Override onResize
const origOnResize = nodeType.prototype.onResize;
nodeType.prototype.onResize = function(size) {
if (origOnResize) {
origOnResize.apply(this, arguments);
}
this._syncTextareaSize();
};
// Serialize node state
const origOnSerialize = nodeType.prototype.onSerialize;
nodeType.prototype.onSerialize = function(info) {
if (origOnSerialize) {
origOnSerialize.apply(this, arguments);
}
info.properties = info.properties || {};
info.properties.text = this._tv_original_text || "";
info.properties.pretty_json_text = this._tv_pretty_json_text || "";
info.properties.theme = this.properties.theme;
info.properties.word_wrap = this.properties.word_wrap;
info.properties.text_filter = this.properties.text_filter;
info.properties.line_filter = this.properties.line_filter;
info.properties.pretty_json_mode = this.properties.pretty_json_mode;
info.properties.font_size = this.properties.font_size;
// Save filter inputs
if (this._tv_filter_input) {
info.properties.filter_text = this._tv_filter_input.value;
}
if (this._tv_line_filter_input) {
info.properties.line_filter_text = this._tv_line_filter_input.value;
}
};
// Deserialize node state
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function(info) {
if (origOnConfigure) {
origOnConfigure.apply(this, arguments);
}
if (info.properties) {
// Restore text content
if (info.properties.text) {
this._tv_original_text = info.properties.text;
this._tv_pretty_json_text = info.properties.pretty_json_text || "";
if (this._tv_box) {
if (info.properties.pretty_json_mode) {
this._tv_box.value = this._tv_pretty_json_text;
} else {
this._tv_box.value = this._tv_original_text;
}
this._updateCounter();
}
}
if (info.properties.font_size) {
this.properties.font_size = info.properties.font_size;
// Apply saved font size
if (this._tv_box) this._tv_box.style.fontSize = `${this.properties.font_size}px`;
if (this._tv_highlight_div) this._tv_highlight_div.style.fontSize = `${this.properties.font_size}px`;
if (this._tv_markdown_div) this._tv_markdown_div.style.fontSize = `${this.properties.font_size}px`;
}
// Restore filter states
if (this._tv_filter_input && info.properties.filter_text) {
this._tv_filter_input.value = info.properties.filter_text;
this._applyFilter();
}
if (this._tv_line_filter_input && info.properties.line_filter_text) {
this._tv_line_filter_input.value = info.properties.line_filter_text;
this._applyLineFilter();
}
// Restore UI states
if (this.properties.text_filter && this._tv_highlight_controls) {
this._tv_highlight_controls.style.display = "flex";
this._tv_highlight_div.style.display = "block";
}
if (this.properties.line_filter && this._tv_filter_controls) {
this._tv_filter_controls.style.display = "flex";
}
this._updateTextPadding();
}
};
// Override onExecuted
const origOnExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (msg) {
try {
const textData = msg?.text;
if (textData && Array.isArray(textData) && textData.length > 0) {
this._tv_set?.(textData[0]);
}
} catch (e) {
console.error("TextViewer onExecuted error:", e);
}
try {
origOnExecuted?.apply(this, arguments);
} catch {}
};
}
});