From 781cea83a0f8b79adb832951b1f9779ff77310d5 Mon Sep 17 00:00:00 2001 From: DominikDoom Date: Sat, 12 Jul 2025 18:29:06 +0200 Subject: [PATCH] Isolate textarea utils in sub-namespace --- javascript/_textAreas.js | 382 ++++++++++++++++++---------------- javascript/tagAutocomplete.js | 26 +-- 2 files changed, 211 insertions(+), 197 deletions(-) diff --git a/javascript/_textAreas.js b/javascript/_textAreas.js index 267d2fb..d76b5fd 100644 --- a/javascript/_textAreas.js +++ b/javascript/_textAreas.js @@ -1,204 +1,218 @@ // Utility functions to select text areas the script should work on, // including third party options. -// Supported third party options so far: -// - Dataset Tag Editor -// Core text area selectors -const core = [ - "#txt2img_prompt > label > textarea", - "#img2img_prompt > label > textarea", - "#txt2img_neg_prompt > label > textarea", - "#img2img_neg_prompt > label > textarea", - ".prompt > label > textarea", - "#txt2img_edit_style_prompt > label > textarea", - "#txt2img_edit_style_neg_prompt > label > textarea", - "#img2img_edit_style_prompt > label > textarea", - "#img2img_edit_style_neg_prompt > label > textarea" -]; +TAC.TextAreas = new (function () { + // Core text area selectors + const core = [ + "#txt2img_prompt > label > textarea", + "#img2img_prompt > label > textarea", + "#txt2img_neg_prompt > label > textarea", + "#img2img_neg_prompt > label > textarea", + ".prompt > label > textarea", + "#txt2img_edit_style_prompt > label > textarea", + "#txt2img_edit_style_neg_prompt > label > textarea", + "#img2img_edit_style_prompt > label > textarea", + "#img2img_edit_style_neg_prompt > label > textarea", + ]; -// Third party text area selectors -const thirdParty = { - "dataset-tag-editor": { - "base": "#tab_dataset_tag_editor_interface", - "hasIds": false, - "selectors": [ - "Caption of Selected Image", - "Interrogate Result", - "Edit Caption", - "Edit Tags" - ] - }, - "image browser": { - "base": "#tab_image_browser", - "hasIds": false, - "selectors": [ - "Filename keyword search", - "EXIF keyword search" - ] - }, - "tab_tagger": { - "base": "#tab_tagger", - "hasIds": false, - "selectors": [ - "Additional tags (split by comma)", - "Exclude tags (split by comma)" - ] - }, - "tiled-diffusion-t2i": { - "base": "#txt2img_script_container", - "hasIds": true, - "onDemand": true, - "selectors": [ - "[id^=MD-t2i][id$=prompt] textarea", - "[id^=MD-t2i][id$=prompt] input[type='text']" - ] - }, - "tiled-diffusion-i2i": { - "base": "#img2img_script_container", - "hasIds": true, - "onDemand": true, - "selectors": [ - "[id^=MD-i2i][id$=prompt] textarea", - "[id^=MD-i2i][id$=prompt] input[type='text']" - ] - }, - "adetailer-t2i": { - "base": "#txt2img_script_container", - "hasIds": true, - "onDemand": true, - "selectors": [ - "[id^=script_txt2img_adetailer_ad_prompt] textarea", - "[id^=script_txt2img_adetailer_ad_negative_prompt] textarea" - ] - }, - "adetailer-i2i": { - "base": "#img2img_script_container", - "hasIds": true, - "onDemand": true, - "selectors": [ - "[id^=script_img2img_adetailer_ad_prompt] textarea", - "[id^=script_img2img_adetailer_ad_negative_prompt] textarea" - ] - }, - "deepdanbooru-object-recognition": { - "base": "#tab_deepdanboru_object_recg_tab", - "hasIds": false, - "selectors": [ - "Found tags", - ] - }, - "TIPO": { - "base": "#tab_txt2img", - "hasIds": false, - "selectors": [ - "Tag Prompt" - ] + // Third party text area selectors + const thirdParty = { + "dataset-tag-editor": { + base: "#tab_dataset_tag_editor_interface", + hasIds: false, + selectors: [ + "Caption of Selected Image", + "Interrogate Result", + "Edit Caption", + "Edit Tags", + ], + }, + "image browser": { + base: "#tab_image_browser", + hasIds: false, + selectors: ["Filename keyword search", "EXIF keyword search"], + }, + tab_tagger: { + base: "#tab_tagger", + hasIds: false, + selectors: ["Additional tags (split by comma)", "Exclude tags (split by comma)"], + }, + "tiled-diffusion-t2i": { + base: "#txt2img_script_container", + hasIds: true, + onDemand: true, + selectors: [ + "[id^=MD-t2i][id$=prompt] textarea", + "[id^=MD-t2i][id$=prompt] input[type='text']", + ], + }, + "tiled-diffusion-i2i": { + base: "#img2img_script_container", + hasIds: true, + onDemand: true, + selectors: [ + "[id^=MD-i2i][id$=prompt] textarea", + "[id^=MD-i2i][id$=prompt] input[type='text']", + ], + }, + "adetailer-t2i": { + base: "#txt2img_script_container", + hasIds: true, + onDemand: true, + selectors: [ + "[id^=script_txt2img_adetailer_ad_prompt] textarea", + "[id^=script_txt2img_adetailer_ad_negative_prompt] textarea", + ], + }, + "adetailer-i2i": { + base: "#img2img_script_container", + hasIds: true, + onDemand: true, + selectors: [ + "[id^=script_img2img_adetailer_ad_prompt] textarea", + "[id^=script_img2img_adetailer_ad_negative_prompt] textarea", + ], + }, + "deepdanbooru-object-recognition": { + base: "#tab_deepdanboru_object_recg_tab", + hasIds: false, + selectors: ["Found tags"], + }, + TIPO: { + base: "#tab_txt2img", + hasIds: false, + selectors: ["Tag Prompt"], + }, + }; + + this.getTextAreas = function () { + // First get all core text areas + let textAreas = [...gradioApp().querySelectorAll(core.join(", "))]; + + for (const [key, entry] of Object.entries(thirdParty)) { + if (entry.hasIds) { + // If the entry has proper ids, we can just select them + textAreas = textAreas.concat([ + ...gradioApp().querySelectorAll(entry.selectors.join(", ")), + ]); + } else { + // Otherwise, we have to find the text areas by their adjacent labels + let base = gradioApp().querySelector(entry.base); + + // Safety check + if (!base) continue; + + let allTextAreas = [...base.querySelectorAll("textarea, input[type='text']")]; + + // Filter the text areas where the adjacent label matches one of the selectors + let matchingTextAreas = allTextAreas.filter((ta) => + [...ta.parentElement.childNodes].some((x) => + entry.selectors.includes(x.innerText) + ) + ); + textAreas = textAreas.concat(matchingTextAreas); + } + } + + return textAreas; } -} -function getTextAreas() { - // First get all core text areas - let textAreas = [...gradioApp().querySelectorAll(core.join(", "))]; + this.addOnDemandObservers = function (setupFunction) { + for (const [key, entry] of Object.entries(thirdParty)) { + if (!entry.onDemand) continue; - for (const [key, entry] of Object.entries(thirdParty)) { - if (entry.hasIds) { // If the entry has proper ids, we can just select them - textAreas = textAreas.concat([...gradioApp().querySelectorAll(entry.selectors.join(", "))]); - } else { // Otherwise, we have to find the text areas by their adjacent labels let base = gradioApp().querySelector(entry.base); - - // Safety check if (!base) continue; - let allTextAreas = [...base.querySelectorAll("textarea, input[type='text']")]; + let accordions = [...base?.querySelectorAll(".gradio-accordion")]; + if (!accordions) continue; - // Filter the text areas where the adjacent label matches one of the selectors - let matchingTextAreas = allTextAreas.filter(ta => [...ta.parentElement.childNodes].some(x => entry.selectors.includes(x.innerText))); - textAreas = textAreas.concat(matchingTextAreas); - } - }; - - return textAreas; -} - -function addOnDemandObservers(setupFunction) { - for (const [key, entry] of Object.entries(thirdParty)) { - if (!entry.onDemand) continue; - - let base = gradioApp().querySelector(entry.base); - if (!base) continue; - - let accordions = [...base?.querySelectorAll(".gradio-accordion")]; - if (!accordions) continue; - - accordions.forEach(acc => { - let accObserver = new MutationObserver((mutationList, observer) => { - for (const mutation of mutationList) { - if (mutation.type === "childList") { - let newChildren = mutation.addedNodes; - if (!newChildren) { - accObserver.disconnect(); - continue; - } - - newChildren.forEach(child => { - if (child.classList.contains("gradio-accordion") || child.querySelector(".gradio-accordion")) { - let newAccordions = [...child.querySelectorAll(".gradio-accordion")]; - newAccordions.forEach(nAcc => accObserver.observe(nAcc, { childList: true })); + accordions.forEach((acc) => { + let accObserver = new MutationObserver((mutationList, observer) => { + for (const mutation of mutationList) { + if (mutation.type === "childList") { + let newChildren = mutation.addedNodes; + if (!newChildren) { + accObserver.disconnect(); + continue; } - }); - if (entry.hasIds) { // If the entry has proper ids, we can just select them - [...gradioApp().querySelectorAll(entry.selectors.join(", "))].forEach(x => setupFunction(x)); - } else { // Otherwise, we have to find the text areas by their adjacent labels - let base = gradioApp().querySelector(entry.base); + newChildren.forEach((child) => { + if ( + child.classList.contains("gradio-accordion") || + child.querySelector(".gradio-accordion") + ) { + let newAccordions = [ + ...child.querySelectorAll(".gradio-accordion"), + ]; + newAccordions.forEach((nAcc) => + accObserver.observe(nAcc, { childList: true }) + ); + } + }); - // Safety check - if (!base) continue; + if (entry.hasIds) { + // If the entry has proper ids, we can just select them + [ + ...gradioApp().querySelectorAll(entry.selectors.join(", ")), + ].forEach((x) => setupFunction(x)); + } else { + // Otherwise, we have to find the text areas by their adjacent labels + let base = gradioApp().querySelector(entry.base); - let allTextAreas = [...base.querySelectorAll("textarea, input[type='text']")]; + // Safety check + if (!base) continue; - // Filter the text areas where the adjacent label matches one of the selectors - let matchingTextAreas = allTextAreas.filter(ta => [...ta.parentElement.childNodes].some(x => entry.selectors.includes(x.innerText))); - matchingTextAreas.forEach(x => setupFunction(x)); + let allTextAreas = [ + ...base.querySelectorAll("textarea, input[type='text']"), + ]; + + // Filter the text areas where the adjacent label matches one of the selectors + let matchingTextAreas = allTextAreas.filter((ta) => + [...ta.parentElement.childNodes].some((x) => + entry.selectors.includes(x.innerText) + ) + ); + matchingTextAreas.forEach((x) => setupFunction(x)); + } } } - } + }); + accObserver.observe(acc, { childList: true }); }); - accObserver.observe(acc, { childList: true }); - }); - }; -} - -const thirdPartyIdSet = new Set(); -// Get the identifier for the text area to differentiate between positive and negative -function getTextAreaIdentifier(textArea) { - let txt2img_p = gradioApp().querySelector('#txt2img_prompt > label > textarea'); - let txt2img_n = gradioApp().querySelector('#txt2img_neg_prompt > label > textarea'); - let img2img_p = gradioApp().querySelector('#img2img_prompt > label > textarea'); - let img2img_n = gradioApp().querySelector('#img2img_neg_prompt > label > textarea'); - - let modifier = ""; - switch (textArea) { - case txt2img_p: - modifier = ".txt2img.p"; - break; - case txt2img_n: - modifier = ".txt2img.n"; - break; - case img2img_p: - modifier = ".img2img.p"; - break; - case img2img_n: - modifier = ".img2img.n"; - break; - default: - // If the text area is not a core text area, it must be a third party text area - // Add it to the set of third party text areas and get its index as a unique identifier - if (!thirdPartyIdSet.has(textArea)) - thirdPartyIdSet.add(textArea); - - modifier = `.thirdParty.ta${[...thirdPartyIdSet].indexOf(textArea)}`; - break; + } } - return modifier; -} + + const thirdPartyIdSet = new Set(); + // Get the identifier for the text area to differentiate between positive and negative + this.getTextAreaIdentifier = function (textArea) { + let txt2img_p = gradioApp().querySelector("#txt2img_prompt > label > textarea"); + let txt2img_n = gradioApp().querySelector("#txt2img_neg_prompt > label > textarea"); + let img2img_p = gradioApp().querySelector("#img2img_prompt > label > textarea"); + let img2img_n = gradioApp().querySelector("#img2img_neg_prompt > label > textarea"); + + let modifier = ""; + switch (textArea) { + case txt2img_p: + modifier = ".txt2img.p"; + break; + case txt2img_n: + modifier = ".txt2img.n"; + break; + case img2img_p: + modifier = ".img2img.p"; + break; + case img2img_n: + modifier = ".img2img.n"; + break; + default: + // If the text area is not a core text area, it must be a third party text area + // Add it to the set of third party text areas and get its index as a unique identifier + if (!thirdPartyIdSet.has(textArea)) thirdPartyIdSet.add(textArea); + + modifier = `.thirdParty.ta${[...thirdPartyIdSet].indexOf(textArea)}`; + break; + } + return modifier; + } +})(); diff --git a/javascript/tagAutocomplete.js b/javascript/tagAutocomplete.js index 5769cb9..ecabd8f 100644 --- a/javascript/tagAutocomplete.js +++ b/javascript/tagAutocomplete.js @@ -332,7 +332,7 @@ function createResultsDiv(textArea) { let sideDiv = document.createElement("div"); let sideDivImg = document.createElement("img"); - let textAreaId = getTextAreaIdentifier(textArea); + let textAreaId = TAC.TextAreas.getTextAreaIdentifier(textArea); let typeClass = textAreaId.replaceAll(".", " "); parentDiv.setAttribute("class", `autocompleteParent${typeClass}`); @@ -354,12 +354,12 @@ function createResultsDiv(textArea) { // Show or hide the results div function isVisible(textArea) { - let textAreaId = getTextAreaIdentifier(textArea); + let textAreaId = TAC.TextAreas.getTextAreaIdentifier(textArea); let parentDiv = gradioApp().querySelector('.autocompleteParent' + textAreaId); return parentDiv.style.display === "flex"; } function showResults(textArea) { - let textAreaId = getTextAreaIdentifier(textArea); + let textAreaId = TAC.TextAreas.getTextAreaIdentifier(textArea); let parentDiv = gradioApp().querySelector('.autocompleteParent' + textAreaId); parentDiv.style.display = "flex"; @@ -383,7 +383,7 @@ function showResults(textArea) { previewDiv.style.display = "none"; } function hideResults(textArea) { - let textAreaId = getTextAreaIdentifier(textArea); + let textAreaId = TAC.TextAreas.getTextAreaIdentifier(textArea); let resultsDiv = gradioApp().querySelector('.autocompleteParent' + textAreaId); if (!resultsDiv) return; @@ -513,7 +513,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout if (name && name.length > 0) { // Check if it's a negative prompt - let textAreaId = getTextAreaIdentifier(textArea); + let textAreaId = TAC.TextAreas.getTextAreaIdentifier(textArea); let isNegative = textAreaId.includes("n"); // Sanitize name for API call name = encodeURIComponent(name) @@ -675,7 +675,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout } function addResultsToList(textArea, results, tagword, resetList) { - let textAreaId = getTextAreaIdentifier(textArea); + let textAreaId = TAC.TextAreas.getTextAreaIdentifier(textArea); let resultDiv = gradioApp().querySelector('.autocompleteResults' + textAreaId); let resultsList = resultDiv.querySelector('ul'); @@ -950,7 +950,7 @@ function addResultsToList(textArea, results, tagword, resetList) { } async function updateSelectionStyle(textArea, newIndex, oldIndex, scroll = true) { - let textAreaId = getTextAreaIdentifier(textArea); + let textAreaId = TAC.TextAreas.getTextAreaIdentifier(textArea); let resultDiv = gradioApp().querySelector('.autocompleteResults' + textAreaId); let resultsList = resultDiv.querySelector('ul'); let items = resultsList.getElementsByTagName('li'); @@ -999,9 +999,9 @@ function updateRuby(textArea, prompt) { if (!TAC.CFG.translation.liveTranslation) return; if (!TAC.CFG.translation.translationFile || TAC.CFG.translation.translationFile === "None") return; - let ruby = gradioApp().querySelector('.acRuby' + getTextAreaIdentifier(textArea)); + let ruby = gradioApp().querySelector('.acRuby' + TAC.TextAreas.getTextAreaIdentifier(textArea)); if (!ruby) { - let textAreaId = getTextAreaIdentifier(textArea); + let textAreaId = TAC.TextAreas.getTextAreaIdentifier(textArea); let typeClass = textAreaId.replaceAll(".", " "); ruby = document.createElement("div"); ruby.setAttribute("class", `acRuby${typeClass} notranslate`); @@ -1309,7 +1309,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) { }); // Check if it's a negative prompt - let textAreaId = getTextAreaIdentifier(textArea); + let textAreaId = TAC.TextAreas.getTextAreaIdentifier(textArea); let isNegative = textAreaId.includes("n"); // Request use counts from the DB @@ -1492,7 +1492,7 @@ async function refreshEmbeddings() { function addAutocompleteToArea(area) { // Return if autocomplete is disabled for the current area type in config - let textAreaId = getTextAreaIdentifier(area); + let textAreaId = TAC.TextAreas.getTextAreaIdentifier(area); if ((!TAC.CFG.activeIn.img2img && textAreaId.includes("img2img")) || (!TAC.CFG.activeIn.txt2img && textAreaId.includes("txt2img")) || (!TAC.CFG.activeIn.negativePrompts && textAreaId.includes("n")) @@ -1550,10 +1550,10 @@ async function setup() { await TacUtils.processQueue(TAC.Ext.QUEUE_FILE_LOAD, null); // Find all textareas - let textAreas = getTextAreas(); + let textAreas = TAC.TextAreas.getTextAreas(); // Add mutation observer to accordions inside a base that has onDemand set to true - addOnDemandObservers(addAutocompleteToArea); + TAC.TextAreas.addOnDemandObservers(addAutocompleteToArea); // Add event listener to apply settings button so we can mirror the changes to our internal config let applySettingsButton = gradioApp().querySelector("#tab_settings #settings_submit") || gradioApp().querySelector("#tab_settings > div > .gr-button-primary");