(function($, elementor){ 'use strict'; // Debug check for KingAddonsAiField // Check if settings exist if (!window.KingAddonsAiField || typeof window.KingAddonsAiField !== 'object') { window.KingAddonsAiField = window.KingAddonsAiField || { ajax_url: ajaxurl || '', generate_action: 'king_addons_ai_generate_text', generate_nonce: '', change_action: 'king_addons_ai_change_text', change_nonce: '', settings_url: '' }; } // Track active observers to avoid duplicates var activeObservers = []; // Track token usage var tokenUsageData = { initialized: false, dailyUsed: 0, dailyLimit: 0, limitReached: false, apiKeyValid: false }; // Function to check if the token limit has been reached function checkTokenLimit(callback) { if (tokenUsageData.initialized) { if (callback) { callback({ limitReached: tokenUsageData.limitReached, apiKeyValid: Boolean(tokenUsageData.apiKeyValid), }); } // Return true if either the token limit is reached or the API key is invalid return tokenUsageData.limitReached || !tokenUsageData.apiKeyValid; } // Fetch current token usage data $.post(KingAddonsAiField.ajax_url, { action: 'king_addons_ai_check_tokens', nonce: KingAddonsAiField.generate_nonce }, function(response) { if (response.success && response.data) { tokenUsageData.initialized = true; tokenUsageData.dailyUsed = parseInt(response.data.daily_used || 0); tokenUsageData.dailyLimit = parseInt(response.data.daily_limit || 0); tokenUsageData.limitReached = response.data.limit_reached === true; tokenUsageData.apiKeyValid = response.data.api_key_valid === true; if (callback) { callback({ limitReached: tokenUsageData.limitReached, apiKeyValid: tokenUsageData.apiKeyValid, }); } } else { // If there's an error, assume both token limit and API key are OK if (callback) { callback({ limitReached: false, apiKeyValid: true, }); } } }).fail(function() { // On failure, assume both token limit and API key are OK if (callback) { callback({ limitReached: false, apiKeyValid: true, }); } }); } // Update token usage data after API calls function updateTokenUsage(usageData) { if (usageData && typeof usageData === 'object') { tokenUsageData.initialized = true; tokenUsageData.dailyUsed = parseInt(usageData.daily_used || 0); tokenUsageData.dailyLimit = parseInt(usageData.daily_limit || 0); tokenUsageData.limitReached = tokenUsageData.dailyLimit > 0 && tokenUsageData.dailyUsed >= tokenUsageData.dailyLimit; } } // Add CSS for animations directly to the document function injectAnimationStyles() { if ($('#king-addons-ai-animations').length === 0) { const animationStyles = ` `; $('head').append(animationStyles); } } // Function to inject AI buttons function injectAiButtons($container) { // Inject animation styles first injectAnimationStyles(); // Handle standard text/textarea controls $container.find('.elementor-control-type-text label.elementor-control-title, .elementor-control-type-textarea label.elementor-control-title').each(function(){ var $label = $(this); var $ctrlWrap = $label.closest('.elementor-control'); // Skip CSS ID, CSS Classes, and inline-label controls if ( $ctrlWrap.hasClass('elementor-control-_element_id') || $ctrlWrap.hasClass('elementor-control-_css_classes') || $ctrlWrap.hasClass('elementor-label-inline') ) { return; } var $input = $ctrlWrap.find('input[type="text"], textarea'); // Check if we already have buttons wrapper after this label if (!$input.length || $label.next('.king-addons-ai-buttons-wrapper').length) { return; } createAndAttachButtons($label, $input, $ctrlWrap, true); }); // Handle WYSIWYG controls $container.find('.elementor-control-type-wysiwyg .elementor-control-input-wrapper').each(function(){ var $inputWrapper = $(this); var $ctrlWrap = $inputWrapper.closest('.elementor-control'); var $textarea = $ctrlWrap.find('textarea.elementor-wp-editor'); // Find the actual textarea // Check if we already have buttons wrapper before this input wrapper if (!$textarea.length || $ctrlWrap.find('.king-addons-ai-buttons-wrapper').length) { return; } // For WYSIWYG, add buttons to the control wrapper before the input wrapper createAndAttachButtons($ctrlWrap, $textarea, $ctrlWrap, false); }); } // Function to create and attach AI buttons function createAndAttachButtons($attachTarget, $field, $ctrlWrap, attachAfterLabel) { var fieldName = $field.attr('name') || $field.attr('id') || ''; // Use ID for WYSIWYG if name is not present var isWysiwyg = $ctrlWrap.hasClass('elementor-control-type-wysiwyg'); // Create wrapper div for buttons var $buttonsWrapper = $('
'); // For WYSIWYG we need different placement if (isWysiwyg) { // Find the input wrapper which contains the editor var $inputWrapper = $ctrlWrap.find('.elementor-control-input-wrapper'); if (!$inputWrapper.length) return; } // Create "Generate" button var $btnLabel = $('').css({ verticalAlign: 'middle', padding: '4px 8px', fontSize: '12px', cursor: 'pointer', background: 'linear-gradient(135deg, #E1CBFF, #5B03FF)', border: 'none', borderRadius: '4px', color: '#ffffff', display: 'inline-flex', alignItems: 'center', // boxShadow: '0 0 6px rgba(225,203,255,0.7), 0 0 12px rgba(91,3,255,0.5)', boxShadow: 'none', transition: 'box-shadow 0.3s ease' }); $btnLabel.append( $('') && !value.includes('
' + para.trim() + '
'; }) .join(''); } else { // Split by double newlines and wrap in paragraph tags value = value.split(paragraphDelimiter).map(function(paragraph) { // Handle single newlines within a paragraph as' + paragraph.trim().replace(/\n/g, '
') + '
') || newContent.includes('
'))) { existingContent = existingContent + '
'; } else if (existingContent) { // Simple text mode, add double line break existingContent = existingContent + '\n\n'; } $field.val(existingContent + newContent); } else { $field.val(value); } } } else { // TinyMCE not available or editor not initialized if (appendMode) { // Append the new content to the original $field.val(originalContent + '\n\n' + value); } else { $field.val(value); } } } catch (e) { console.error('King Addons: Error updating WYSIWYG content', e); // Fallback - set the textarea value directly if (appendMode) { // Maintain HTML structure in fallback case var existingContent = originalContent || ''; var newContent = value || ''; // Add paragraph separator if needed if (existingContent && !existingContent.trim().endsWith('') && (existingContent.includes('') || newContent.includes('
'))) { existingContent = existingContent + '
'; } else if (existingContent) { existingContent = existingContent + '\n\n'; } $field.val(existingContent + newContent); } else { $field.val(value); } } } else { // Standard text field if (appendMode) { // Append the new content to the original for text fields $field.val(originalContent + '\n\n' + value); } else { $field.val(value); } } // Trigger change events to ensure Elementor detects the change $field.trigger('input'); $field.trigger('change'); // For WYSIWYG, also try to trigger a TinyMCE change event if available if (isWysiwyg && window.tinymce && tinymce.get($field.attr('id'))) { try { tinymce.get($field.attr('id')).fire('change'); } catch (e) { console.error('King Addons: Error triggering TinyMCE change event', e); } } } // Function to get field value (handles WYSIWYG) function getFieldValue($field, isWysiwyg) { if (isWysiwyg) { var editorId = $field.attr('id'); if (window.tinymce && tinymce.get(editorId) && !tinymce.get(editorId).isHidden()) { // Visual mode return tinymce.get(editorId).getContent(); } else { // Text mode or editor not initialized return $field.val(); } } return $field.val(); } // Function to setup MutationObserver to detect controls changes function setupControlsObserver(panel) { // Observe the entire panel for any control changes (e.g., section tabs) var $controlsContainer = panel.$el; activeObservers.forEach(function(observer) { observer.disconnect(); }); activeObservers = []; var observer = new MutationObserver(function(mutations) { // Use a small delay to allow Elementor to finish rendering, especially for complex controls setTimeout(function() { injectAiButtons($controlsContainer); }, 50); }); observer.observe($controlsContainer[0], { childList: true, subtree: true }); activeObservers.push(observer); // Initial injection, with a delay setTimeout(function() { injectAiButtons($controlsContainer); }, 150); } // On widget panel open, setup the observer elementor.hooks.addAction('panel/open_editor/widget', function(panel) { setTimeout(function() { setupControlsObserver(panel); }, 250); // Increased delay for initial setup }); // Also monitor section changes elementor.channels.editor.on('section:activated', function(sectionName, editor) { var panel = editor.getOption('editedElementView').getContainer().panel; if (panel && panel.$el) { // When a section is activated, reinitialize observer and injection setTimeout(function() { setupControlsObserver(panel); }, 150); // Delay for section rendering } }); })(jQuery, window.elementor);