/** * wpForo AI Features - WordPress Content Indexing * * Isolated JavaScript for WordPress content indexing tab. * Completely independent from forum indexing handlers. * * @package wpForo * @subpackage Admin * @since 3.0.0 */ (function($) { 'use strict'; const WpForoWPIndexing = { initialized: false, wpIndexingPollInterval: null, _wpAutoRefreshTimeout: null, /** * Initialize WordPress content indexing features */ init: function() { if (this.initialized) return; // Only init if on WordPress Indexing tab if (!$('.wpforo-ai-wp-indexing-tab').length) return; this.initialized = true; this.bindEvents(); this.loadWPIndexingStatus(); this.checkWPIndexingAutoRefresh(); }, /** * Bind all event handlers */ bindEvents: function() { const self = this; // Refresh status button $(document).on('click', '.wpforo-ai-wp-refresh-status', this.handleRefreshWPStatus.bind(this)); // Taxonomy dropdown change $(document).on('change', '#wp-taxonomy-select', this.handleTaxonomyChange.bind(this)); // Form submissions $(document).on('submit', '.wpforo-ai-wp-taxonomy-form', this.handleWPTaxonomyIndex.bind(this)); $(document).on('submit', '.wpforo-ai-wp-custom-form', this.handleWPCustomIndex.bind(this)); $(document).on('submit', '.wpforo-ai-wp-ids-form', this.handleWPIndexByIds.bind(this)); // Clear index button $(document).on('click', '.wpforo-ai-wp-clear-index', this.handleWPClearIndex.bind(this)); // Cleanup session button (WordPress-specific) $(document).on('click', '.wpforo-ai-wp-cleanup-session', this.handleCleanupSession.bind(this)); // Toggle switches $(document).on('change', '#wpforo-ai-wp-auto-indexing', this.handleWPAutoIndexingToggle.bind(this)); $(document).on('change', '#wpforo-ai-wp-image-indexing', this.handleWPImageIndexingToggle.bind(this)); // Select All / Deselect All for terms $(document).on('click', '.wpforo-ai-wp-select-all-terms', function() { $('#wp-terms-container input[type="checkbox"]').prop('checked', true); self.updateTermIndexButton(); }); $(document).on('click', '.wpforo-ai-wp-deselect-all-terms', function() { $('#wp-terms-container input[type="checkbox"]').prop('checked', false); self.updateTermIndexButton(); }); }, /** * Refresh WordPress indexing status */ handleRefreshWPStatus: function(e) { e.preventDefault(); const self = this; const $button = $(e.currentTarget); const $icon = $button.find('.dashicons-update'); $icon.addClass('wpforo-spin'); $button.prop('disabled', true); this.loadWPIndexingStatus(function() { $icon.removeClass('wpforo-spin'); $button.prop('disabled', false); }); }, /** * Load WordPress indexing status from API */ loadWPIndexingStatus: function(callback) { const self = this; $.ajax({ url: wpforoAIAdmin.ajaxUrl, type: 'POST', data: { action: 'wpforo_ai_wp_get_indexing_status', security: wpforoAIAdmin.adminNonce }, success: function(response) { if (response.success && response.data) { self.updateWPIndexingDisplay(response.data); // Start polling if indexing is in progress if (response.data.queue && response.data.queue.status === 'processing') { self.startWPIndexingPolling(); } else { self.stopWPIndexingPolling(); } } }, error: function(xhr, status, error) { console.error('Failed to load WordPress indexing status:', error); }, complete: function() { if (typeof callback === 'function') { callback(); } } }); }, /** * Start polling for WordPress indexing status */ startWPIndexingPolling: function() { const self = this; // Don't start if already polling if (this.wpIndexingPollInterval) { return; } // Poll every 5 seconds this.wpIndexingPollInterval = setInterval(function() { self.loadWPIndexingStatus(); }, 5000); }, /** * Stop polling for WordPress indexing status */ stopWPIndexingPolling: function() { if (this.wpIndexingPollInterval) { clearInterval(this.wpIndexingPollInterval); this.wpIndexingPollInterval = null; } }, /** * Start auto page refresh for WordPress content indexing. */ startWPIndexingAutoRefresh: function() { // Store flag to indicate we're in auto-refresh mode try { localStorage.setItem('wpforo_wp_indexing_auto_refresh', '1'); } catch (e) { /* localStorage may be blocked */ } console.log('WP content indexing: reloading page to trigger cron nudge...'); window.location.hash = 'wp-indexing-status-box'; window.location.reload(); }, /** * Check on page load if auto-refresh should continue. */ checkWPIndexingAutoRefresh: function() { const self = this; // Check if we're in auto-refresh mode let inAutoRefresh = false; try { inAutoRefresh = localStorage.getItem('wpforo_wp_indexing_auto_refresh') === '1'; } catch (e) { /* localStorage may be blocked */ } if (!inAutoRefresh) { return; } // Check current indexing status $.ajax({ url: wpforoAIAdmin.ajaxUrl, type: 'POST', data: { action: 'wpforo_ai_wp_get_indexing_status', security: wpforoAIAdmin.adminNonce }, success: function(response) { if (response.success && response.data) { // Check if still processing if (response.data.queue && response.data.queue.status === 'processing') { console.log('WP indexing in progress, will refresh in 20 seconds...'); // Schedule next refresh in 20 seconds self._wpAutoRefreshTimeout = setTimeout(function() { window.location.hash = 'wp-indexing-status-box'; window.location.reload(); }, 20000); } else { // Done - clear auto-refresh flag and do final reload // to ensure server-rendered HTML shows correct state console.log('WP content indexing complete, final reload'); self.stopWPIndexingAutoRefresh(); window.location.hash = 'wp-indexing-status-box'; window.location.reload(); } } }, error: function() { // On error, stop auto-refresh to avoid infinite reload loop self.stopWPIndexingAutoRefresh(); } }); }, /** * Stop auto page refresh and clear the flag. */ stopWPIndexingAutoRefresh: function() { try { localStorage.removeItem('wpforo_wp_indexing_auto_refresh'); } catch (e) { /* localStorage may be blocked */ } if (this._wpAutoRefreshTimeout) { clearTimeout(this._wpAutoRefreshTimeout); this._wpAutoRefreshTimeout = null; } }, /** * Update WordPress indexing display with status data */ updateWPIndexingDisplay: function(data) { // Update total indexed if (data.total_indexed !== undefined) { $('#wp-total-indexed').text(data.total_indexed.toLocaleString()); // Update coverage percentage const totalContent = parseInt($('#wp-total-content').text().replace(/,/g, ''), 10) || 0; if (totalContent > 0) { const percentage = Math.round((data.total_indexed / totalContent) * 100 * 10) / 10; $('#wp-indexed-percentage').text(percentage + '%'); } } // Update by_type counts if (data.by_type) { for (const [type, info] of Object.entries(data.by_type)) { const postType = type.replace('wp_', ''); const $indexed = $('#wp-indexed-' + postType + ' .indexed-count'); if ($indexed.length) { $indexed.text(info.indexed || 0); } } } // Update last activity if (data.last_indexed_at_formatted) { $('#wp-last-activity').text(data.last_indexed_at_formatted); } else if (!data.last_indexed_at) { $('#wp-last-activity').text(wpforoAIAdmin.strings?.noActivity || 'No activity yet'); } // Get status elements const $statusElement = $('#wp-indexing-status'); // Update status with spinner animation if (data.queue && data.queue.status === 'processing') { $statusElement.html( ' ' + (wpforoAIAdmin.strings?.indexing || 'Indexing...') ).removeClass('status-idle').addClass('status-active'); } else { $statusElement.html( ' ' + (wpforoAIAdmin.strings?.idle || 'Idle') ).removeClass('status-active').addClass('status-idle'); } }, /** * Handle taxonomy dropdown change - load terms as checkboxes */ handleTaxonomyChange: function(e) { const self = this; const taxonomy = $(e.currentTarget).val(); const $termsContainer = $('#wp-terms-container'); const $termsActions = $('#wp-terms-actions'); const $indexBtn = $('.wpforo-ai-wp-index-taxonomy'); if (!taxonomy) { $termsContainer.html('
Select a taxonomy first to load terms...
'); $termsActions.hide(); $indexBtn.prop('disabled', true); return; } $termsContainer.html('
Loading terms...
'); $termsActions.hide(); $.ajax({ url: wpforoAIAdmin.ajaxUrl, type: 'POST', data: { action: 'wpforo_ai_wp_get_taxonomy_terms', security: wpforoAIAdmin.adminNonce, taxonomy: taxonomy }, success: function(response) { if (response.success && response.data && response.data.terms) { const terms = response.data.terms; if (terms.length === 0) { $termsContainer.html('
No terms found in this taxonomy.
'); $termsActions.hide(); $indexBtn.prop('disabled', true); return; } let html = '
'; terms.forEach(function(term) { const indexed = term.indexed || 0; const total = term.count || 0; html += self.renderTermCheckbox(term, indexed, total, false); // Add children if any if (term.children && term.children.length) { term.children.forEach(function(child) { const childIndexed = child.indexed || 0; const childTotal = child.count || 0; html += self.renderTermCheckbox(child, childIndexed, childTotal, true); }); } }); html += '
'; $termsContainer.html(html); $termsActions.show(); // Bind checkbox change events $termsContainer.find('input[type="checkbox"]').on('change', function() { self.updateTermIndexButton(); }); self.updateTermIndexButton(); } else { $termsContainer.html('
Error loading terms.
'); $termsActions.hide(); } }, error: function() { $termsContainer.html('
Error loading terms.
'); $termsActions.hide(); } }); }, /** * Render a single term checkbox item */ renderTermCheckbox: function(term, indexed, total, isChild) { const itemClass = isChild ? 'wpforo-ai-term-checkbox-item wpforo-ai-term-child' : 'wpforo-ai-term-checkbox-item'; return ''; }, /** * Update the index button state based on selected terms */ updateTermIndexButton: function() { const $indexBtn = $('.wpforo-ai-wp-index-taxonomy'); const checkedCount = $('#wp-terms-container input[type="checkbox"]:checked').length; $indexBtn.prop('disabled', checkedCount === 0); }, /** * Escape HTML special characters */ escapeHtml: function(text) { const div = document.createElement('div'); div.appendChild(document.createTextNode(text)); return div.innerHTML; }, /** * Handle taxonomy-based indexing form submission */ handleWPTaxonomyIndex: function(e) { e.preventDefault(); const self = this; const $form = $(e.currentTarget); const $button = $form.find('.wpforo-ai-wp-index-taxonomy'); const taxonomy = $form.find('#wp-taxonomy-select').val(); // Collect all selected term IDs from checkboxes const termIds = []; $('#wp-terms-container input[type="checkbox"]:checked').each(function() { termIds.push($(this).val()); }); if (!taxonomy || termIds.length === 0) { alert('Please select a taxonomy and at least one term.'); return; } // Get selected post types const postTypes = []; $('.wpforo-ai-wp-type-checkbox:checked').each(function() { postTypes.push($(this).val()); }); if (postTypes.length === 0) { alert('Please select at least one content type.'); return; } $button.prop('disabled', true).text('Indexing...'); // Build request data including optional date range const requestData = { action: 'wpforo_ai_wp_index_by_taxonomy', security: wpforoAIAdmin.adminNonce, taxonomy: taxonomy, term_ids: termIds, post_types: postTypes }; // Add date range if specified const dateFrom = $form.find('#wp-tax-date-from').val(); const dateTo = $form.find('#wp-tax-date-to').val(); if (dateFrom) requestData.date_from = dateFrom; if (dateTo) requestData.date_to = dateTo; $.ajax({ url: wpforoAIAdmin.ajaxUrl, type: 'POST', data: requestData, success: function(response) { if (response.success) { // Start auto page refresh - each refresh triggers inline cron nudge self.startWPIndexingAutoRefresh(); } else { alert('Error: ' + (response.data?.message || 'Unknown error')); $button.prop('disabled', false).html(' Index Selected Terms'); } }, error: function() { alert('Error starting indexing. Please try again.'); $button.prop('disabled', false).html(' Index Selected Terms'); } }); }, /** * Handle custom indexing form submission */ handleWPCustomIndex: function(e) { e.preventDefault(); const self = this; const $form = $(e.currentTarget); const $button = $form.find('.wpforo-ai-wp-index-custom'); // Get selected post types from within this form const postTypes = []; $form.find('.wpforo-ai-wp-type-checkbox:checked').each(function() { postTypes.push($(this).val()); }); if (postTypes.length === 0) { alert('Please select at least one content type.'); return; } const data = { action: 'wpforo_ai_wp_index_custom', security: wpforoAIAdmin.adminNonce, post_types: postTypes, date_from: $form.find('#wp-date-from').val(), date_to: $form.find('#wp-date-to').val() }; $button.prop('disabled', true).text('Indexing...'); $.ajax({ url: wpforoAIAdmin.ajaxUrl, type: 'POST', data: data, success: function(response) { if (response.success) { // Start auto page refresh - each refresh triggers inline cron nudge self.startWPIndexingAutoRefresh(); } else { alert('Error: ' + (response.data?.message || 'Unknown error')); $button.prop('disabled', false).html(' Index Selected Content'); } }, error: function() { alert('Error starting indexing. Please try again.'); $button.prop('disabled', false).html(' Index Selected Content'); } }); }, /** * Handle index by specific IDs form submission */ handleWPIndexByIds: function(e) { e.preventDefault(); const self = this; const $form = $(e.currentTarget); const $button = $form.find('.wpforo-ai-wp-index-ids'); const postIds = $form.find('#wp-post-ids').val().trim(); if (!postIds) { alert('Please enter at least one post ID.'); return; } const data = { action: 'wpforo_ai_wp_index_custom', security: wpforoAIAdmin.adminNonce, post_ids: postIds }; $button.prop('disabled', true).text('Indexing...'); $.ajax({ url: wpforoAIAdmin.ajaxUrl, type: 'POST', data: data, success: function(response) { if (response.success) { $form.find('#wp-post-ids').val(''); // Clear the field // Start auto page refresh - each refresh triggers inline cron nudge self.startWPIndexingAutoRefresh(); } else { alert('Error: ' + (response.data?.message || 'Unknown error')); $button.prop('disabled', false).html(' Index by IDs'); } }, error: function() { alert('Error starting indexing. Please try again.'); $button.prop('disabled', false).html(' Index by IDs'); } }); }, /** * Handle Clear WordPress index button */ handleWPClearIndex: function(e) { e.preventDefault(); const self = this; const $button = $(e.currentTarget); const confirmMessage = $button.data('confirm'); if (!confirm(confirmMessage)) { return; } $button.prop('disabled', true).text('Clearing...'); $.ajax({ url: wpforoAIAdmin.ajaxUrl, type: 'POST', data: { action: 'wpforo_ai_wp_delete_content', security: wpforoAIAdmin.adminNonce, delete_all: 'true' }, success: function(response) { if (response.success) { // Stop any auto-refresh cycle self.stopWPIndexingAutoRefresh(); // Reload page to show updated stats window.location.hash = 'wp-indexing-status-box'; window.location.reload(); } else { alert('Error: ' + (response.data?.message || 'Unknown error')); $button.prop('disabled', false).html(' Clear WordPress Index'); } }, error: function() { alert('Error clearing index. Please try again.'); $button.prop('disabled', false).html(' Clear WordPress Index'); } }); }, /** * Handle WordPress auto-indexing toggle change */ handleWPAutoIndexingToggle: function(e) { const $input = $(e.currentTarget); const isEnabled = $input.is(':checked') ? 1 : 0; const optionName = $input.data('option-name') || 'ai_wp_auto_indexing_enabled'; const $toggle = $input.closest('.wpforo-ai-auto-index-toggle'); // Disable the toggle during AJAX request $input.prop('disabled', true); $toggle.css('opacity', '0.6'); // Save via AJAX $.ajax({ url: wpforoAIAdmin.ajaxUrl, type: 'POST', data: { action: 'wpforo_ai_save_wp_indexing_option', nonce: wpforoAIAdmin.nonce, option_name: optionName, enabled: isEnabled }, success: function(response) { $input.prop('disabled', false); $toggle.css('opacity', '1'); if (!response.success) { // Revert the change on failure $input.prop('checked', !isEnabled); alert('Error: ' + (response.data?.message || 'Failed to save setting')); } }, error: function() { $input.prop('disabled', false); $toggle.css('opacity', '1'); $input.prop('checked', !isEnabled); alert('Error saving setting. Please try again.'); } }); }, /** * Handle WordPress image indexing toggle change */ handleWPImageIndexingToggle: function(e) { const $input = $(e.currentTarget); const isEnabled = $input.is(':checked') ? 1 : 0; const optionName = $input.data('option-name') || 'ai_wp_image_indexing_enabled'; const $toggle = $input.closest('.wpforo-ai-auto-index-toggle'); // Show confirmation when enabling (due to credit impact) if (isEnabled) { const confirmed = confirm( 'Enable Image Indexing for WordPress Content?\n\n' + 'When enabled, posts with images will consume +1 additional credit during indexing.\n\n' + '• Maximum 10 images per post are processed\n' + '• Images are converted to text descriptions for search\n' + '• Small images (< 50x50px) like smileys are skipped\n\n' + 'Continue?' ); if (!confirmed) { $input.prop('checked', false); return; } } // Disable the toggle during AJAX request $input.prop('disabled', true); $toggle.css('opacity', '0.6'); // Save via AJAX $.ajax({ url: wpforoAIAdmin.ajaxUrl, type: 'POST', data: { action: 'wpforo_ai_save_wp_indexing_option', nonce: wpforoAIAdmin.nonce, option_name: optionName, enabled: isEnabled }, success: function(response) { $input.prop('disabled', false); $toggle.css('opacity', '1'); if (!response.success) { // Revert the change on failure $input.prop('checked', !isEnabled); alert('Error: ' + (response.data?.message || 'Failed to save setting')); } }, error: function() { $input.prop('disabled', false); $toggle.css('opacity', '1'); $input.prop('checked', !isEnabled); alert('Error saving setting. Please try again.'); } }); }, /** * Handle WordPress-specific cleanup session */ handleCleanupSession: function(e) { e.preventDefault(); const self = this; const $button = $(e.currentTarget); const confirmMsg = $button.data('confirm') || 'Reset stuck WordPress indexing session?'; if (!window.confirm(confirmMsg)) { return; } const originalHtml = $button.html(); $button.prop('disabled', true).html(' Cleaning up...'); // Clear browser-side state try { localStorage.removeItem('wpforo_wp_indexing_auto_refresh'); } catch (err) { /* localStorage may be blocked */ } this.stopWPIndexingAutoRefresh(); $.ajax({ url: wpforoAIAdmin.ajaxUrl, type: 'POST', data: { action: 'wpforo_ai_cleanup_indexing_session', scope: 'wp', _wpnonce: wpforoAIAdmin.nonce }, success: function(response) { $button.prop('disabled', false).html(originalHtml); if (response && response.success) { // Reload to refresh all server-rendered counts window.location.hash = 'wp-indexing-status-box'; window.location.reload(); } else { const msg = (response && response.data && response.data.message) || 'Cleanup failed.'; window.alert(msg); } }, error: function(xhr, status, error) { $button.prop('disabled', false).html(originalHtml); console.error('Cleanup indexing session failed:', error); window.alert('Cleanup failed. Check the browser console for details.'); } }); } }; // Initialize when DOM ready $(document).ready(function() { WpForoWPIndexing.init(); }); // Expose globally for debugging window.WpForoWPIndexing = WpForoWPIndexing; })(jQuery);