From f68c0c7dc2003ff4b3712e654f7802d5ee36a83a Mon Sep 17 00:00:00 2001 From: newtextdoc1111 Date: Thu, 20 Nov 2025 22:00:15 +0900 Subject: [PATCH] feat: Implement auto-formatting block logic --- web/js/auto-formatter.js | 65 ++++++++++++++++++++++++++++++++-- web/js/main.js | 75 ++++++++++++++++++++++++++++++++++------ web/js/node-info.js | 14 ++++++++ 3 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 web/js/node-info.js diff --git a/web/js/auto-formatter.js b/web/js/auto-formatter.js index f48b3d1..afc6bbe 100644 --- a/web/js/auto-formatter.js +++ b/web/js/auto-formatter.js @@ -1,4 +1,59 @@ import { settingValues } from './settings.js'; +import { NodeInfo } from './node-info.js'; + + +/** + * Determines if the text content should be auto-formatted. + * + * Format conditions: + * 1. Skip formatting if node is in blocklist + * 2. Skip formatting if text contains only numbers, hyphens, commas, dots, and spaces + * 3. Format if text contains "word + comma" pattern at least twice + * 4. Otherwise, don't format + * + * @param {NodeInfo} nodeInfo - The node information. + * @returns {boolean} - True if the text should be formatted, false otherwise. + */ +function shouldAutoFormat(text, nodeInfo) { + if (!text || text.trim().length === 0) return false; + + // 1. Check if the node name is in the blocklist + const blocklist = [ + ["Power Puter (rgthree)", "code"], + ["LoraLoaderBlockWeight //Inspire", "block_vector"] + ]; + + const isBlocklisted = blocklist.some(([type, input]) => + type === nodeInfo.nodeType && input === nodeInfo.inputName + ); + + if (isBlocklisted) { + // console.debug(`[Autocomplete-Plus] auto-formatter on blur => nodeType: ${nodeInfo.nodeType}, inputName: ${nodeInfo.inputName} => blocklisted`); + return false; + } else { + // console.debug(`[Autocomplete-Plus] auto-formatter on blur => nodeType: ${nodeInfo.nodeType}, inputName: ${nodeInfo.inputName}`); + } + + + const trimmedText = text.trim(); + + // 2. Check if the text is purely numeric data with hyphens and commas + // (e.g., "0,0,0,1,1,1" or "0.5, -1.2, 0.8" for LoRA Block Weight) + const numericPattern = /^[\d.,\s-]+$/; + if (numericPattern.test(trimmedText)) { + return false; // Don't format numeric data + } + + // 3. Check if the text contains the pattern "word + comma" + const wordCommaPattern = /\w+\s*,/g; + const matches = trimmedText.match(wordCommaPattern); + + if (matches == null) { + return false; + } + + return true; // Text should be formatted +} /** * Format the prompt text: add a comma and space after each tag, and remove extra spaces. @@ -79,12 +134,18 @@ export class AutoFormatterEventHandler { * Handle blur event to trigger auto-formatting * @param {Event} event - The blur event */ - handleBlur(event) { + handleBlur(event, nodeInfo) { if ( settingValues.enableAutoFormat && event.target.tagName === 'TEXTAREA' ) { - formatTextareaOnBlur(event.target); + const textarea = event.target; + const text = textarea.value; + + // Check if the content should be auto-formatted + if (shouldAutoFormat(text, nodeInfo)) { + formatTextareaOnBlur(textarea); + } } } diff --git a/web/js/main.js b/web/js/main.js index e6e3836..29fcd1c 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -7,6 +7,7 @@ import { TagSource, loadDataAsync } from "./data.js"; import { AutocompleteEventHandler } from "./autocomplete.js"; import { RelatedTagsEventHandler } from "./related-tags.js"; import { AutoFormatterEventHandler } from "./auto-formatter.js"; +import { NodeInfo } from "./node-info.js"; // --- Constants --- const id = "AutocompletePlus"; @@ -20,11 +21,11 @@ function initializeEventHandlers() { const autocompleteEventHandler = new AutocompleteEventHandler(); const relatedTagsEventHandler = new RelatedTagsEventHandler(); const autoFormatterEventHandler = new AutoFormatterEventHandler(); - const attachedElements = new WeakSet(); // Keep track of elements that have listeners attached + const attachedElementNodeInfoMap = new WeakMap(); // Map to track attached elements and their node info // Function to attach listeners - function attachListeners(element) { - if (attachedElements.has(element)) return; // Prevent double attachment + function attachListeners(element, nodeInfo) { + if (attachedElementNodeInfoMap.has(element)) return; // Prevent double attachment element.addEventListener('input', handleInput); element.addEventListener('focus', handleFocus); @@ -37,7 +38,7 @@ function initializeEventHandlers() { element.addEventListener('mousemove', handleMouseMove); element.addEventListener('click', handleClick); - attachedElements.add(element); // Mark as attached + attachedElementNodeInfoMap.set(element, nodeInfo); // Mark as attached and store node info } // Attempt Widget Override as the primary method @@ -50,12 +51,14 @@ function initializeEventHandlers() { // Check if the widget has an inputEl and if it's a TEXTAREA // This is to ensure we are targeting multiline text inputs, related to '.comfy-multiline-input' - if (result && result.widget && result.widget.inputEl && result.widget.inputEl.tagName === 'TEXTAREA') { + if (result && result.widget + && result.widget.inputEl && result.widget.inputEl.tagName === 'TEXTAREA' && !result.widget.inputEl.readOnly) { const widgetConfig = inputData && inputData[1] ? inputData[1] : {}; // Future: Add checks for Autocomplete Plus specific configurations if needed // e.g., if (widgetConfig["AutocompletePlus.enabled"] === false) return result; - attachListeners(result.widget.inputEl); + const nodeInfo = new NodeInfo(node.comfyClass || node.constructor.name, inputName); + attachListeners(result.widget.inputEl, nodeInfo); } return result; }; @@ -75,9 +78,11 @@ function initializeEventHandlers() { targetSelectors.forEach(selector => { // Check if the added node itself matches or contains matching elements if (node.matches(selector)) { - attachListeners(node); + attachListeners(node, new NodeInfo('Fallback', 'unknown')); } else { - node.querySelectorAll(selector).forEach(attachListeners); + node.querySelectorAll(selector).forEach(el => { + attachListeners(el, new NodeInfo('Fallback', 'unknown')); + }); } }); } @@ -87,7 +92,9 @@ function initializeEventHandlers() { // Initial scan for existing elements targetSelectors.forEach(selector => { - document.querySelectorAll(selector).forEach(attachListeners); + document.querySelectorAll(selector).forEach(el => { + attachListeners(el, new NodeInfo('Fallback', 'unknown')); + }); }); // Start observing the document body for changes @@ -95,41 +102,89 @@ function initializeEventHandlers() { } function handleInput(event) { + const nodeInfo = attachedElementNodeInfoMap.get(event.target); + if (!nodeInfo) { + console.warn('[Autocomplete-Plus] Node info not found for element in handleInput:', event.target); + return; + } + autocompleteEventHandler.handleInput(event); relatedTagsEventHandler.handleInput(event); + autoFormatterEventHandler.handleInput(event); } function handleFocus(event) { + const nodeInfo = attachedElementNodeInfoMap.get(event.target); + if (!nodeInfo) { + console.warn('[Autocomplete-Plus] Node info not found for element in handleFocus:', event.target); + return; + } + autocompleteEventHandler.handleFocus(event); relatedTagsEventHandler.handleFocus(event); + autoFormatterEventHandler.handleFocus(event); } function handleBlur(event) { + const nodeInfo = attachedElementNodeInfoMap.get(event.target); + if (!nodeInfo) { + console.warn('[Autocomplete-Plus] Node info not found for element in handleBlur:', event.target); + return; + } + autocompleteEventHandler.handleBlur(event); relatedTagsEventHandler.handleBlur(event); - autoFormatterEventHandler.handleBlur(event); + autoFormatterEventHandler.handleBlur(event, nodeInfo); } function handleKeyDown(event) { + const nodeInfo = attachedElementNodeInfoMap.get(event.target); + if (!nodeInfo) { + console.warn('[Autocomplete-Plus] Node info not found for element in handleKeyDown:', event.target); + return; + } + autocompleteEventHandler.handleKeyDown(event); relatedTagsEventHandler.handleKeyDown(event); + autoFormatterEventHandler.handleKeyDown(event); } function handleKeyUp(event) { + const nodeInfo = attachedElementNodeInfoMap.get(event.target); + if (!nodeInfo) { + console.warn('[Autocomplete-Plus] Node info not found for element in handleKeyUp:', event.target); + return; + } + autocompleteEventHandler.handleKeyUp(event); relatedTagsEventHandler.handleKeyUp(event); + autoFormatterEventHandler.handleKeyUp(event); } // New event handler for mousemove to show related tags on hover function handleMouseMove(event) { + const nodeInfo = attachedElementNodeInfoMap.get(event.target); + if (!nodeInfo) { + console.warn('[Autocomplete-Plus] Node info not found for element in handleMouseMove:', event.target); + return; + } + autocompleteEventHandler.handleMouseMove(event); relatedTagsEventHandler.handleMouseMove(event); + autoFormatterEventHandler.handleMouseMove(event); } // New event handler for click to show related tags function handleClick(event) { + const nodeInfo = attachedElementNodeInfoMap.get(event.target); + if (!nodeInfo) { + console.warn('[Autocomplete-Plus] Node info not found for element in handleClick:', event.target); + return; + } + autocompleteEventHandler.handleClick(event); relatedTagsEventHandler.handleClick(event); + autoFormatterEventHandler.handleClick(event); } } diff --git a/web/js/node-info.js b/web/js/node-info.js new file mode 100644 index 0000000..f112d38 --- /dev/null +++ b/web/js/node-info.js @@ -0,0 +1,14 @@ +/** + * Class to hold information about the node attached to an input element. + * Used to control behavior based on node information. + */ +export class NodeInfo { + /** + * @param {string} nodeType - The type/class name of the node + * @param {string} inputName - The name of the input widget + */ + constructor(nodeType, inputName) { + this.nodeType = nodeType; + this.inputName = inputName; + } +}