import { NotesApi } from "../api/notesApi.js"; export class AppendMenu { constructor(textArea, onContentChange) { this.textArea = textArea; this.onContentChange = onContentChange; this.container = null; this.button = null; this.submenu = null; this.isDisabled = true; } createMenuElements() { // Append submenu container this.container = document.createElement('div'); this.container.className = 'notes-submenu-container'; this.button = document.createElement('button'); this.button.innerHTML = ''; this.button.className = 'notes-btn'; this.button.title = 'Append'; this.button.onclick = () => this.toggleMenu(); this.button.disabled = this.isDisabled; // Create submenu this.submenu = document.createElement('div'); this.submenu.className = 'notes-submenu'; this.submenu.style.display = 'none'; // Create submenu items const rawOption = document.createElement('div'); rawOption.className = 'notes-submenu-item'; rawOption.textContent = 'Append list of LoRA filenames'; rawOption.onclick = () => this.selectOption('raw'); const tagOption = document.createElement('div'); tagOption.className = 'notes-submenu-item'; tagOption.textContent = "Append list of LoRAs in '' format"; tagOption.onclick = () => this.selectOption('tag'); const metaOption = document.createElement('div'); metaOption.className = 'notes-submenu-item'; metaOption.textContent = "Append list of LoRAs in ', metadata' format"; metaOption.onclick = () => this.selectOption('meta'); this.submenu.appendChild(rawOption); this.submenu.appendChild(tagOption); this.submenu.appendChild(metaOption); this.container.appendChild(this.button); this.container.appendChild(this.submenu); return this.container; } setDisabled(disabled) { this.isDisabled = disabled; if (this.button) { this.button.disabled = disabled; } } toggleMenu() { if (this.submenu.style.display === 'none') { this.submenu.style.display = 'block'; // Add click listener to close menu when clicking outside document.addEventListener('click', this.closeMenuHandler); } else { this.submenu.style.display = 'none'; document.removeEventListener('click', this.closeMenuHandler); } } closeMenuHandler = (event) => { if (!this.container.contains(event.target)) { this.submenu.style.display = 'none'; document.removeEventListener('click', this.closeMenuHandler); } } selectOption(type) { this.submenu.style.display = 'none'; document.removeEventListener('click', this.closeMenuHandler); this.appendLoras(type); } async appendLoras(formatType) { try { let formattedText; switch(formatType) { case 'raw': formattedText = await this.formatLorasRaw(); break; case 'tag': formattedText = await this.formatLorasTags(); break; case 'meta': formattedText = await this.formatLorasWithMetadata(); break; default: throw new Error(`Unknown format type: ${formatType}`); } this.insertTextIntoTextarea(formattedText); } catch (error) { console.error('Error appending LoRAs:', error); alert(`Failed to append LoRAs ${formatType === 'raw' ? 'list' : formatType === 'tag' ? 'tags' : 'with metadata'}`); } } async formatLorasRaw() { const loras = await NotesApi.getLoras(); // Normalize backslashes to forward slashes const normalizedLoras = loras.map(lora => lora.replace(/\\/g, '/')); return normalizedLoras.join('\n'); } async formatLorasTags() { const loras = await NotesApi.getLoras(); // Format each LoRA as , stripping .safetensors extension and normalizing slashes const lorasTags = loras.map(lora => { const nameWithoutExt = lora.replace(/\.safetensors$/i, ''); const normalizedPath = nameWithoutExt.replace(/\\/g, '/'); return ``; }); return lorasTags.join('\n'); } async formatLorasWithMetadata() { const loras = await NotesApi.getLorasWithMetadata(); // Format each LoRA with metadata as , metadata const lorasWithMeta = loras.map(lora => { const nameWithoutExt = lora.name.replace(/\.safetensors$/i, ''); const normalizedPath = nameWithoutExt.replace(/\\/g, '/'); const baseTag = ``; if (lora.metadata) { return `${baseTag}, ${lora.metadata}`; } else { return baseTag; } }); return lorasWithMeta.join('\n'); } insertTextIntoTextarea(text) { if (!this.textArea) return; // Add newline before appending if current content doesn't end with one const separator = this.textArea.value && !this.textArea.value.endsWith('\n') ? '\n' : ''; const textToInsert = separator + text; // Focus textarea to ensure it's active this.textArea.focus(); // Move cursor to end this.textArea.setSelectionRange(this.textArea.value.length, this.textArea.value.length); // Use document.execCommand to preserve undo history document.execCommand('insertText', false, textToInsert); // Trigger change handling if (this.onContentChange) { this.onContentChange(); } } }