/** * This separates out all create logic from the base TaxonomySelector.js, so that we only enqueue create logic if it's creatable * Updated to work with the refactored centralized TaxonomySelector */ class TaxonomyCreator { constructor(selector) { this.selector = selector; // Get taxonomy from current active field config this.taxonomy = selector.currentConfig?.taxonomy; if (!this.taxonomy) { console.error('TaxonomyCreator: No active field or taxonomy found'); return; } this.createNew = selector.modal.querySelector('.create-new-term'); this.toggle = selector.modal.querySelector('.new-term-toggle'); this.form = this.createNew.querySelector('.create-new-term-section'); this.initListeners(); this.initTermCreation(); } initListeners() { document.addEventListener('click', this.handleClick.bind(this)); } handleClick(e) { if (window.targetCheck(e, '.create-new-term summary')) { if (this.createNew.open) { this.createNew.querySelector('input[name="term_name"]').focus(); } this.resetParentOptions(); } if (window.targetCheck(e, '.submit-term')) { this.handleTermCreation(e); } } async handleTermCreation(e) { const termName = this.form.querySelector('input[name="term_name"]').value.trim(); const parentId = this.form.querySelector('input#select_parent')?.value; try { this.form.querySelector('button').disabled = true; const response = await this.createTerm(termName, parentId); if (response.success) { let term = response.term; // Close the create new section this.createNew.open = false; // Add to the terms list UI this.selector.createTermElement({ id: parseInt(term.id), name: term.name, hasChildren: term.hasChildren || false, path: term.path || term.name, show: false }); // Add to current modal selection this.selector.addSelectedTermToModal(term.id, term.name, term.path); // Clear the form this.form.querySelector('input[name="term_name"]').value = ''; } } catch (error) { console.error('Error creating term:', error); this.selector.showError?.('Failed to create term') || console.error('Failed to create term'); } finally { this.form.querySelector('button').disabled = false; } } initTermCreation() { if (!this.form) { return; } this.form.addEventListener('change', (e) => { e.preventDefault(); e.stopPropagation(); }); } resetParentOptions() { let select = this.createNew.querySelector('#select_parent'); if (!select) return; let defaultOption = select.querySelector('option'); if (!defaultOption) return; // Clear existing options window.removeChildren(select); select.append(defaultOption.cloneNode(true)); // Add current parent if we're in a sub-category if (this.selector.currentParentName !== '') { let parentOption = defaultOption.cloneNode(true); parentOption.value = this.selector.currentParent; parentOption.textContent = this.selector.currentParentName; select.append(parentOption); } // Add terms from current taxonomy cache const taxonomyTerms = this.selector.currentTerms; if (taxonomyTerms && taxonomyTerms.length > 0) { taxonomyTerms.forEach(term => { let option = defaultOption.cloneNode(true); option.id = `select-parent-${term.id}`; option.value = term.id; option.textContent = ' — ' + term.name; select.append(option); }); } } async createTerm(name, parent = 0) { let loadingMessage = this.createNew.querySelector('.loading-message.create-term'); let text = loadingMessage?.querySelector('span'); try { if (loadingMessage) { loadingMessage.hidden = false; } if (text && window.typeText) { window.typeText(text, 'Checking term...'); } else if (text) { text.textContent = 'Checking term...'; } // Check if term already exists by searching const originalSearchQuery = this.selector.searchQuery; const originalFetchSpecific = this.selector.fetchSpecificTerms; this.selector.searchQuery = name; this.selector.fetchSpecificTerms = false; // We want to search, not fetch specific IDs const existingTerms = await this.selector.fetchTerms( this.selector.activeField, false, true // isSearch = true ); // Restore original search state this.selector.searchQuery = originalSearchQuery; this.selector.fetchSpecificTerms = originalFetchSpecific; // Check if any existing terms match exactly const exactMatches = existingTerms.filter(term => term.name.toLowerCase() === name.toLowerCase() ); if (exactMatches.length > 0) { this.showTermSuggestions(exactMatches); return { success: false, reason: 'exists' }; } // Show similar terms if found if (existingTerms.length > 0) { this.showTermSuggestions(existingTerms); return { success: false, reason: 'similar' }; } // Term doesn't exist, create it if (text) { text.textContent = 'Creating term...'; } const response = await fetch(`${jvbSettings.api}terms`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': jvbSettings.nonce }, body: JSON.stringify({ taxonomy: this.taxonomy, name: name, parent: parent }) }); if (!response.ok) { throw new Error(`Server error: ${response.status}`); } const result = await response.json(); return result; } catch (error) { console.error('Error creating term:', error); throw error; } finally { this.form.querySelector('button').disabled = false; if (loadingMessage) { loadingMessage.hidden = true; } } } // Helper method to show term suggestions when similar terms exist showTermSuggestions(suggestions) { const suggestionContainer = this.createNew.querySelector('.term-suggestions') || this.createSuggestionContainer(); // Clear existing suggestions window.removeChildren(suggestionContainer); // Add heading const heading = document.createElement('h4'); heading.textContent = 'Similar terms already exist:'; suggestionContainer.appendChild(heading); // Create list of suggestions const list = document.createElement('ul'); list.className = 'term-suggestion-list'; suggestions.forEach(term => { const item = document.createElement('li'); // Create term path display if available let termDisplay = term.path || term.name; const button = document.createElement('button'); button.type = 'button'; button.className = 'use-existing-term'; button.setAttribute('data-id', term.id); button.textContent = termDisplay; button.addEventListener('click', () => { // Add this term to modal selection this.selector.addSelectedTermToModal(term.id, term.name, term.path); // Close the create new section this.createNew.open = false; // Clear suggestions suggestionContainer.hidden = true; // Clear the form this.form.querySelector('input[name="term_name"]').value = ''; }); item.appendChild(button); list.appendChild(item); }); suggestionContainer.appendChild(list); suggestionContainer.hidden = false; } // Create container for term suggestions if it doesn't exist createSuggestionContainer() { const container = document.createElement('div'); container.className = 'term-suggestions'; container.hidden = true; // Insert after the form this.createNew.querySelector('form').after(container); return container; } /** * Clean up when modal closes */ destroy() { // Remove event listeners if needed // Clear any pending operations const loadingMessage = this.createNew?.querySelector('.loading-message.create-term'); if (loadingMessage) { loadingMessage.hidden = true; } // Clear suggestions const suggestionContainer = this.createNew?.querySelector('.term-suggestions'); if (suggestionContainer) { suggestionContainer.hidden = true; } } } window.jvbTaxCreator = TaxonomyCreator;