From 2cd74b17504cc6da0baa9fe56c545b824bd5963f Mon Sep 17 00:00:00 2001 From: newtextdoc1111 Date: Sat, 3 May 2025 10:35:13 +0900 Subject: [PATCH] Refactor event handling functions --- web/js/autocomplete.js | 185 ++++++++++++++++------------------------ web/js/event-handler.js | 63 -------------- web/js/helper.js | 15 +++- web/js/main.js | 104 +++++++++++++++++++--- web/js/settings.js | 4 + 5 files changed, 183 insertions(+), 188 deletions(-) delete mode 100644 web/js/event-handler.js diff --git a/web/js/autocomplete.js b/web/js/autocomplete.js index 13f2d4f..f1eafa6 100644 --- a/web/js/autocomplete.js +++ b/web/js/autocomplete.js @@ -8,10 +8,6 @@ import { escapeParentheses, unescapeParentheses } from './helper.js'; -import { - SimilarTagsEventHandler, -} from './similar-tags.js'; -import { registerEventHandlers } from './event-handler.js'; // --- Autocomplete UI Class --- @@ -470,7 +466,6 @@ class AutocompleteUI { // --- Autocomplete Logic --- const autocompleteUI = new AutocompleteUI(); -const similarTagsEventHandler = new SimilarTagsEventHandler(); // Pass the autocomplete UI instance /** * Finds tag completion candidates based on the input query. @@ -642,116 +637,82 @@ function insertTag(inputElement, tagToInsert) { inputElement.dispatchEvent(new Event('input', { bubbles: true })); } -// --- Event Handlers --- - -function handleInput(event) { - if (!settingValues.enabled || !autocompleteUI) return; - - const ESCAPE_SEQUENCE = ["#", "/"]; - const textareaElement = event.target; - const partialTag = getCurrentPartialTag(textareaElement); - if (partialTag.length > 0 && !ESCAPE_SEQUENCE.some(seq => partialTag.startsWith(seq))) { - const candidates = findCompletionCandidates(partialTag); - autocompleteUI.show(textareaElement, candidates); - } else { - autocompleteUI.hide(); - } - - similarTagsEventHandler.handleInput(event); -} - -function handleFocus(event) { - if (!settingValues.enabled) return; - // Potentially show suggestions immediately on focus? - // For now, only show on input - if (!autocompleteUI) { - autocompleteUI = new AutocompleteUI(); - } - // Maybe check if there's already text and show suggestions? - // handleInput(event); // Trigger check immediately - - similarTagsEventHandler.handleFocus(event); -} - -function handleBlur(event) { - // Need a slight delay because clicking the autocomplete list causes blur - setTimeout(() => { - if (autocompleteUI && !autocompleteUI.element.contains(document.activeElement)) { +export class AutocompleteEventHandler { + handleInput(event) { + if (!settingValues.enabled || !autocompleteUI) return; + + const ESCAPE_SEQUENCE = ["#", "/"]; // prevent autocomplete for these sequences + const textareaElement = event.target; + const partialTag = getCurrentPartialTag(textareaElement); + if (partialTag.length > 0 && !ESCAPE_SEQUENCE.some(seq => partialTag.startsWith(seq))) { + const candidates = findCompletionCandidates(partialTag); + autocompleteUI.show(textareaElement, candidates); + } else { autocompleteUI.hide(); } - - similarTagsEventHandler.handleBlur(event); - }, 150); -} - -function handleKeyDown(event) { - if (!settingValues.enabled) return; - - const textareaElement = event.target; - - // Handle autocomplete navigation - if (autocompleteUI && autocompleteUI.isVisible()) { - switch (event.key) { - case 'ArrowDown': - event.preventDefault(); - autocompleteUI.navigate(1); - break; - case 'ArrowUp': - event.preventDefault(); - autocompleteUI.navigate(-1); - break; - case 'Enter': - case 'Tab': - if (autocompleteUI.getSelectedTag() !== null) { - event.preventDefault(); - insertTag(textareaElement, autocompleteUI.getSelectedTag()); - } else { - // Allow default Tab/Enter if no item is selected - autocompleteUI.hide(); - } - break; - case 'Escape': - event.preventDefault(); + } + + handleFocus(event) { + if (!settingValues.enabled) return; + // Potentially show suggestions immediately on focus? + // For now, only show on input + if (!autocompleteUI) { + autocompleteUI = new AutocompleteUI(); + } + // Maybe check if there's already text and show suggestions? + // handleInput(event); // Trigger check immediately + } + + handleBlur(event) { + // Need a slight delay because clicking the autocomplete list causes blur + setTimeout(() => { + if (autocompleteUI && !autocompleteUI.element.contains(document.activeElement)) { autocompleteUI.hide(); - break; + } + }, 150); + } + + handleKeyDown(event) { + if (!settingValues.enabled) return; + + const textareaElement = event.target; + + // Handle autocomplete navigation + if (autocompleteUI && autocompleteUI.isVisible()) { + switch (event.key) { + case 'ArrowDown': + event.preventDefault(); + autocompleteUI.navigate(1); + break; + case 'ArrowUp': + event.preventDefault(); + autocompleteUI.navigate(-1); + break; + case 'Enter': + case 'Tab': + if (autocompleteUI.getSelectedTag() !== null) { + event.preventDefault(); + insertTag(textareaElement, autocompleteUI.getSelectedTag()); + } else { + // Allow default Tab/Enter if no item is selected + autocompleteUI.hide(); + } + break; + case 'Escape': + event.preventDefault(); + autocompleteUI.hide(); + break; + } } } - - similarTagsEventHandler.handleKeyDown(event); -} - -// New event handler for mousemove to show similar tags on hover -function handleMouseMove(event) { - similarTagsEventHandler.handleMouseMove(event); -} - -// New event handler for click to show similar tags -function handleClick(event) { - if (!settingValues.enabled || !settingValues.enableSimilarTags || - settingValues.similarTagsDisplayMode !== 'click') return; - - similarTagsEventHandler.handleClick(event); -} - -// --- End Data Loading Functions --- - -// --- Initialization --- - -export async function initializeAutocomplete(rootPath) { - try { - await loadAllData(rootPath); - - registerEventHandlers({ - handleInput: handleInput, - handleFocus: handleFocus, - handleBlur: handleBlur, - handleKeyDown: handleKeyDown, - handleMouseMove: handleMouseMove, - handleClick: handleClick - }); - - console.log("[Autocomplete-Plus] Autocomplete and similar tags features initialized."); - } catch (e) { - console.error("[Autocomplete-Plus] Error during API data loading:", e); + + // New event handler for mousemove to show similar tags on hover + handleMouseMove(event) { } -} + + // New event handler for click to show similar tags + handleClick(event) { + if (!settingValues.enabled || !settingValues.enableSimilarTags || + settingValues.similarTagsDisplayMode !== 'click') return; + } +} \ No newline at end of file diff --git a/web/js/event-handler.js b/web/js/event-handler.js deleted file mode 100644 index b686ce4..0000000 --- a/web/js/event-handler.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Registers event handlers for dynamically added and existing textareas in the DOM. - * This function uses a MutationObserver to monitor changes in the DOM and attaches - * event listeners to elements matching specified selectors. - * - * @param {Object} eventHandlers - An object containing the event handler functions. - * @param {Function} eventHandlers.handleInput - Function to handle the 'input' event. - * @param {Function} eventHandlers.handleFocus - Function to handle the 'focus' event. - * @param {Function} eventHandlers.handleBlur - Function to handle the 'blur' event. - * @param {Function} eventHandlers.handleKeyDown - Function to handle the 'keydown' event. - * @param {Function} eventHandlers.handleMouseMove - Function to handle the 'mousemove' event. - * @param {Function} eventHandlers.handleClick - Function to handle the 'click' event. - */ -export function registerEventHandlers(eventHandlers) { - // Find relevant textareas (e.g., prompt inputs) - // This selector might need adjustment based on ComfyUI's structure - const targetSelectors = [ - '.comfy-multiline-input', - // Add other selectors if needed - ]; - - // Use MutationObserver to detect dynamically added textareas - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - targetSelectors.forEach(selector => { - // Check if the added node itself matches or contains matching elements - if (node.matches(selector)) { - attachListeners(node); - } else { - node.querySelectorAll(selector).forEach(attachListeners); - } - }); - } - }); - }); - }); - - // Function to attach listeners - function attachListeners(element) { - if (element.dataset.autocompleteAttached) return; // Prevent double attachment - - element.addEventListener('input', eventHandlers.handleInput); - element.addEventListener('focus', eventHandlers.handleFocus); - element.addEventListener('blur', eventHandlers.handleBlur); - element.addEventListener('keydown', eventHandlers.handleKeyDown); - - // Add new event listeners for similar tags feature - element.addEventListener('mousemove', eventHandlers.handleMouseMove); - element.addEventListener('click', eventHandlers.handleClick); - - element.dataset.autocompleteAttached = 'true'; - } - - // Initial scan for existing elements - targetSelectors.forEach(selector => { - document.querySelectorAll(selector).forEach(attachListeners); - }); - - // Start observing the document body for changes - observer.observe(document.body, { childList: true, subtree: true }); -} \ No newline at end of file diff --git a/web/js/helper.js b/web/js/helper.js index 2371009..d8b7a50 100644 --- a/web/js/helper.js +++ b/web/js/helper.js @@ -93,4 +93,17 @@ export function unescapeParentheses(str) { if (!str) return str; return str.replace(/\\\(/g, '(').replace(/\\\)/g, ')'); } -// --- End String Helper Functions --- \ No newline at end of file + +// --- End String Helper Functions --- + +// Function to load a CSS file +export function loadCSS(href) { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.type = 'text/css'; + link.href = href; + // Ensure the CSS is loaded before other scripts might rely on its styles + // by adding it to the head. + document.head.appendChild(link); + console.debug(`Loaded CSS: ${href}`); // Optional: Log loading +} diff --git a/web/js/main.js b/web/js/main.js index 3a70782..fa39434 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -1,17 +1,95 @@ import { app } from "/scripts/app.js"; -import { initializeAutocomplete } from './autocomplete.js'; import { settingValues } from "./settings.js"; +import { loadCSS } from "./helper.js"; +import { loadAllData } from "./data.js"; +import { AutocompleteEventHandler } from "./autocomplete.js"; +import { SimilarTagsEventHandler } from "./similar-tags.js"; -// Function to load a CSS file -function loadCSS(href) { - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.type = 'text/css'; - link.href = href; - // Ensure the CSS is loaded before other scripts might rely on its styles - // by adding it to the head. - document.head.appendChild(link); - console.debug(`Loaded CSS: ${href}`); // Optional: Log loading +function initializeEventHandlers() { + // Singletons for event handlers + const autocompleteEventHandler = new AutocompleteEventHandler(); + const similarTagsEventHandler = new SimilarTagsEventHandler(); + + // Find relevant textareas (e.g., prompt inputs) + // This selector might need adjustment based on ComfyUI's structure + const targetSelectors = [ + '.comfy-multiline-input', + // Add other selectors if needed + ]; + + // Use MutationObserver to detect dynamically added textareas + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === Node.ELEMENT_NODE) { + targetSelectors.forEach(selector => { + // Check if the added node itself matches or contains matching elements + if (node.matches(selector)) { + attachListeners(node); + } else { + node.querySelectorAll(selector).forEach(attachListeners); + } + }); + } + }); + }); + }); + + // Function to attach listeners + function attachListeners(element) { + if (element.dataset.autocompleteAttached) return; // Prevent double attachment + + element.addEventListener('input', handleInput); + element.addEventListener('focus', handleFocus); + element.addEventListener('blur', handleBlur); + element.addEventListener('keydown', handleKeyDown); + + // Add new event listeners for similar tags feature + element.addEventListener('mousemove', handleMouseMove); + element.addEventListener('click', handleClick); + + element.dataset.autocompleteAttached = 'true'; + } + + // Initial scan for existing elements + targetSelectors.forEach(selector => { + document.querySelectorAll(selector).forEach(attachListeners); + }); + + // Start observing the document body for changes + observer.observe(document.body, { childList: true, subtree: true }); + + function handleInput(event) { + autocompleteEventHandler.handleInput(event); + similarTagsEventHandler.handleInput(event); + } + + function handleFocus(event) { + autocompleteEventHandler.handleFocus(event); + similarTagsEventHandler.handleFocus(event); + } + + function handleBlur(event) { + autocompleteEventHandler.handleBlur(event); + similarTagsEventHandler.handleBlur(event); + } + + function handleKeyDown(event) { + autocompleteEventHandler.handleKeyDown(event); + similarTagsEventHandler.handleKeyDown(event); + } + + // New event handler for mousemove to show similar tags on hover + function handleMouseMove(event) { + autocompleteEventHandler.handleMouseMove(event); + similarTagsEventHandler.handleMouseMove(event); + } + + // New event handler for click to show similar tags + function handleClick(event) { + autocompleteEventHandler.handleClick(event); + similarTagsEventHandler.handleClick(event); + } } const id = "AutocompletePlus"; @@ -20,10 +98,12 @@ app.registerExtension({ id: id, name: name, setup() { + initializeEventHandlers(); + let rootPath = import.meta.url.replace("js/main.js", ""); loadCSS(rootPath + "css/autocomplete-plus.css"); // Load CSS for autocomplete - initializeAutocomplete(rootPath); // Initialize after tags are loaded + loadAllData(rootPath); }, settings: [ { diff --git a/web/js/settings.js b/web/js/settings.js index d981194..91d85dd 100644 --- a/web/js/settings.js +++ b/web/js/settings.js @@ -5,4 +5,8 @@ export const settingValues = { enableSimilarTags: true, maxSimilarTags: 20, similarTagsDisplayMode: 'click', // 'hover' or 'click' +} + +export function initSettings(){ + } \ No newline at end of file