|
|
|
|
|
|
|
|
|
|
|
class TabsExtension { |
|
|
|
|
|
|
|
|
static #config; |
|
|
|
|
|
|
|
|
|
|
|
static #activeExtension = { "txt": null, "img": null }; |
|
|
|
|
|
|
|
|
|
|
|
static #enablePairs = []; |
|
|
|
|
|
|
|
|
|
|
|
static #refreshQueue = null; |
|
|
|
|
|
|
|
|
static #tryFindEnableToggle(extension) { |
|
|
let temp = null; |
|
|
for (const checkbox of extension.querySelectorAll('input[type=checkbox]')) { |
|
|
const labelText = checkbox.parentNode?.querySelector('span')?.textContent?.toLowerCase(); |
|
|
if (labelText?.includes('enable')) return checkbox; |
|
|
if (!temp && labelText?.includes('active')) temp = checkbox; |
|
|
} |
|
|
return temp; |
|
|
} |
|
|
|
|
|
|
|
|
static #refreshEnableCheckbox() { |
|
|
if (this.#refreshQueue) clearTimeout(this.#refreshQueue); |
|
|
this.#refreshQueue = window.setTimeout(() => { |
|
|
for (const [toggle, button] of this.#enablePairs) { |
|
|
if (toggle.checked) button.classList.add('active'); |
|
|
else button.classList.remove('active'); |
|
|
} |
|
|
}, 250); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static #sort_extensions(extensions, configs) { |
|
|
if (!this.#config.sort) return extensions; |
|
|
const sorted = {}; |
|
|
for (const key of Object.keys(configs)) { |
|
|
if (Object.prototype.hasOwnProperty.call(extensions, key)) { |
|
|
sorted[key] = extensions[key]; |
|
|
delete extensions[key]; |
|
|
} |
|
|
} |
|
|
for (const key of Object.keys(extensions)) sorted[key] = extensions[key]; |
|
|
return sorted; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static #setup_tabs(mode, extensions, configs) { |
|
|
const container = { |
|
|
'left': document.getElementById(`${mode}2img_script_container`), |
|
|
'right': document.getElementById(`${mode}2img_results`) |
|
|
}; |
|
|
|
|
|
const mainSide = configs['tabs']; |
|
|
const oppSide = (mainSide === 'left') ? 'right' : 'left'; |
|
|
|
|
|
|
|
|
if (!container[mainSide] || !container[oppSide]) return configs; |
|
|
|
|
|
|
|
|
const extensionContainers = {}; |
|
|
for (const side of ['above', 'left', 'below', 'right']) { |
|
|
const div = document.createElement("div"); |
|
|
div.id = `tabs_ex_content-${mode}2img-${side}`; |
|
|
div.style.overflow = "visible"; |
|
|
extensionContainers[side] = div; |
|
|
} |
|
|
|
|
|
const buttonContainer = document.createElement("div"); |
|
|
extensionContainers[mainSide].appendChild(buttonContainer); |
|
|
buttonContainer.id = `tabs_ex_${mode}`; |
|
|
|
|
|
container[mainSide].appendChild(extensionContainers['above']); |
|
|
container[mainSide].appendChild(extensionContainers[mainSide]); |
|
|
container[mainSide].appendChild(extensionContainers['below']); |
|
|
container[oppSide].appendChild(extensionContainers[oppSide]); |
|
|
|
|
|
|
|
|
|
|
|
const allButtons = {}; |
|
|
|
|
|
|
|
|
const searchWrapper = document.createElement("div"); |
|
|
searchWrapper.style.display = "flex"; |
|
|
searchWrapper.style.justifyContent = "center"; |
|
|
searchWrapper.style.width = "100%"; |
|
|
searchWrapper.style.marginBottom = "6px"; |
|
|
|
|
|
const searchInput = document.createElement("input"); |
|
|
searchInput.type = "text"; |
|
|
searchInput.placeholder = "Filter extensions..."; |
|
|
searchInput.autocomplete = "off"; |
|
|
searchInput.style.width = "100%"; |
|
|
searchInput.style.maxWidth = "480px"; |
|
|
searchInput.style.minWidth = "240px"; |
|
|
searchInput.style.padding = "6px 10px"; |
|
|
searchInput.style.borderRadius = "6px"; |
|
|
searchInput.style.border = "1px solid var(--block-border-color, #444)"; |
|
|
|
|
|
searchWrapper.appendChild(searchInput); |
|
|
buttonContainer.appendChild(searchWrapper); |
|
|
|
|
|
const self = this; |
|
|
const applyFilter = (queryText) => { |
|
|
const q = (queryText || "").trim().toLowerCase(); |
|
|
for (const [tabKey, btn] of Object.entries(allButtons)) { |
|
|
const label = (btn.textContent || "").toLowerCase(); |
|
|
btn.style.display = (q.length === 0 || label.includes(q)) ? "" : "none"; |
|
|
} |
|
|
|
|
|
const active = self.#activeExtension[mode]; |
|
|
if (active && allButtons[active] && allButtons[active].style.display === "none") { |
|
|
allButtons[active].classList.remove('selected'); |
|
|
if (extensions[active]) extensions[active].style.display = "none"; |
|
|
self.#activeExtension[mode] = null; |
|
|
} |
|
|
}; |
|
|
searchInput.addEventListener("input", (e) => applyFilter(e.target.value)); |
|
|
|
|
|
|
|
|
for (const tabKey of Object.keys(extensions)) { |
|
|
if (!Object.prototype.hasOwnProperty.call(configs, tabKey)) { |
|
|
configs[tabKey] = configs['default']; |
|
|
} |
|
|
|
|
|
const pos = configs[tabKey]; |
|
|
if (pos === "hide") continue; |
|
|
|
|
|
if (pos === "above" || pos === "below") { |
|
|
extensionContainers[pos].appendChild(extensions[tabKey]); |
|
|
extensions[tabKey].style.display = "block"; |
|
|
continue; |
|
|
} |
|
|
|
|
|
|
|
|
const btnSpan = document.createElement('span'); |
|
|
btnSpan.className = 'tab_label'; |
|
|
|
|
|
const extensionName = (this.#config.version) |
|
|
? tabKey |
|
|
: extensions[tabKey].getAttribute("ext-label"); |
|
|
|
|
|
btnSpan.textContent = (!this.#config.forge) |
|
|
? extensionName |
|
|
: (extensionName || "").split('Integrated')[0].trim(); |
|
|
|
|
|
|
|
|
const tabButton = document.createElement("button"); |
|
|
tabButton.classList.add('tab_button'); |
|
|
tabButton.appendChild(btnSpan); |
|
|
|
|
|
buttonContainer.appendChild(tabButton); |
|
|
allButtons[tabKey] = tabButton; |
|
|
|
|
|
tabButton.addEventListener("click", (e) => { |
|
|
|
|
|
if (!this.#config.rmb && (e.ctrlKey || e.metaKey)) return; |
|
|
|
|
|
if (this.#activeExtension[mode] != null) { |
|
|
allButtons[this.#activeExtension[mode]]?.classList.remove('selected'); |
|
|
const prev = this.#activeExtension[mode]; |
|
|
if (prev && extensions[prev]) extensions[prev].style.display = "none"; |
|
|
} |
|
|
|
|
|
this.#activeExtension[mode] = ( |
|
|
(this.#config.toggle) && (this.#activeExtension[mode] === tabKey) |
|
|
) ? null : tabKey; |
|
|
|
|
|
if (this.#activeExtension[mode] != null) { |
|
|
allButtons[this.#activeExtension[mode]]?.classList.add('selected'); |
|
|
extensions[this.#activeExtension[mode]].style.display = "block"; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
extensionContainers[configs[tabKey]].appendChild(extensions[tabKey]); |
|
|
|
|
|
|
|
|
if (tabKey === 'Scripts') { |
|
|
const scriptsDropdown = extensions[tabKey].querySelector('input'); |
|
|
|
|
|
const observer = new MutationObserver(() => { |
|
|
if (!scriptsDropdown) return; |
|
|
if (scriptsDropdown.value !== 'None') allButtons['Scripts']?.classList.add('active'); |
|
|
else allButtons['Scripts']?.classList.remove('active'); |
|
|
}); |
|
|
observer.observe(extensions[tabKey], { childList: true, subtree: true }); |
|
|
|
|
|
if (this.#config.scripts_toggle) { |
|
|
const btn = document.getElementById(`TABSEX_${mode}2img_s_toggle`); |
|
|
if (btn) { |
|
|
if (this.#config.rmb) { |
|
|
allButtons[tabKey].addEventListener("contextmenu", (e) => { |
|
|
e.preventDefault(); btn.click(); return false; |
|
|
}); |
|
|
} else { |
|
|
allButtons[tabKey].addEventListener("click", (e) => { |
|
|
if (e.ctrlKey || e.metaKey) btn.click(); |
|
|
}); |
|
|
} |
|
|
} |
|
|
} |
|
|
continue; |
|
|
} |
|
|
|
|
|
|
|
|
const enableToggle = this.#tryFindEnableToggle(extensions[tabKey]); |
|
|
if (!enableToggle) continue; |
|
|
|
|
|
if (this.#config.rmb) { |
|
|
allButtons[tabKey].addEventListener("contextmenu", (e) => { |
|
|
e.preventDefault(); enableToggle.click(); return false; |
|
|
}); |
|
|
} else { |
|
|
allButtons[tabKey].addEventListener("click", (e) => { |
|
|
if (e.ctrlKey || e.metaKey) enableToggle.click(); |
|
|
}); |
|
|
} |
|
|
|
|
|
if (enableToggle.checked) allButtons[tabKey].classList.add('active'); |
|
|
this.#enablePairs.push([enableToggle, allButtons[tabKey]]); |
|
|
} |
|
|
|
|
|
|
|
|
for (const side of ['above', 'left', 'below', 'right']) { |
|
|
if (extensionContainers[side].children.length === 0) { |
|
|
extensionContainers[side].remove(); |
|
|
} |
|
|
} |
|
|
|
|
|
if (this.#config.open) { |
|
|
const firstBtn = Object.values(allButtons)[0]; |
|
|
if (firstBtn) firstBtn.click(); |
|
|
} |
|
|
|
|
|
return configs; |
|
|
} |
|
|
|
|
|
|
|
|
static init() { |
|
|
this.#config = new TabsExtensionConfigs(); |
|
|
const configs = this.#config.parseConfigs(); |
|
|
const processedConfigs = {}; |
|
|
|
|
|
for (const mode of ['txt', 'img']) { |
|
|
let extensions = null; |
|
|
|
|
|
const keepExtensions = Object.keys(configs[mode]) |
|
|
.filter((ext) => configs[mode][ext] === "keep"); |
|
|
|
|
|
try { |
|
|
const parsed = TabsExtensionParser.parse(mode, keepExtensions); |
|
|
extensions = this.#sort_extensions(parsed, configs[mode]); |
|
|
} catch (e) { |
|
|
alert(`[TabsExtension] Something went wrong while parsing ${mode}2img extensions:\n${e}`); |
|
|
continue; |
|
|
} |
|
|
|
|
|
try { |
|
|
processedConfigs[mode] = this.#setup_tabs(mode, extensions, configs[mode]); |
|
|
if (this.#config.container) { |
|
|
const styler = document.getElementById(`${mode}2img_script_container`) |
|
|
?.querySelector(".styler"); |
|
|
if (styler) styler.style.display = "none"; |
|
|
} |
|
|
} catch (e) { |
|
|
alert(`[TabsExtension] Something went wrong during ${mode}2img setup:\n${e}`); |
|
|
continue; |
|
|
} |
|
|
} |
|
|
|
|
|
try { |
|
|
this.#config.saveConfigs(processedConfigs); |
|
|
} catch (e) { |
|
|
alert(`[TabsExtension] Something went wrong while saving configs:\n${e}`); |
|
|
} |
|
|
|
|
|
document.addEventListener("click", () => this.#refreshEnableCheckbox()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
(function () { |
|
|
onUiLoaded(() => { |
|
|
const slider = document.getElementById("setting_tabs_ex_delay") |
|
|
?.querySelector("input[type=range]"); |
|
|
const delay = parseInt(slider?.value ?? "50", 10); |
|
|
|
|
|
setTimeout(() => { |
|
|
if (typeof TabsExtension?.init === "function") TabsExtension.init(); |
|
|
}, Number.isNaN(delay) ? 50 : delay); |
|
|
}); |
|
|
})(); |
|
|
|