/** * WC Licensed Product - Admin Licenses Live Search * * @package Jeremias\WcLicensedProduct */ (function($) { 'use strict'; var searchTimeout = null; var $searchInput = $('#license-search-input'); var $resultsDropdown = null; var isSearching = false; /** * Initialize live search */ function init() { if (!$searchInput.length) { return; } // Create results dropdown $resultsDropdown = $('
'); $searchInput.parent().css('position', 'relative').append($resultsDropdown); // Bind events $searchInput.on('input', handleSearchInput); $searchInput.on('keydown', handleKeydown); $searchInput.on('focus', function() { if ($resultsDropdown.children().length > 0) { $resultsDropdown.show(); } }); // Close dropdown when clicking outside $(document).on('click', function(e) { if (!$(e.target).closest('.search-box').length) { $resultsDropdown.hide(); } }); } /** * Handle search input with debouncing */ function handleSearchInput() { var query = $searchInput.val().trim(); // Clear previous timeout if (searchTimeout) { clearTimeout(searchTimeout); } // Hide results if query is too short if (query.length < 2) { $resultsDropdown.hide().empty(); return; } // Show loading state showLoading(); // Debounce search searchTimeout = setTimeout(function() { performSearch(query); }, 300); } /** * Handle keyboard navigation */ function handleKeydown(e) { var $items = $resultsDropdown.find('.wclp-search-result-item'); var $active = $items.filter('.active'); var index = $items.index($active); switch (e.keyCode) { case 40: // Down arrow e.preventDefault(); if (index < $items.length - 1) { $items.removeClass('active'); $items.eq(index + 1).addClass('active'); } else if (index === -1 && $items.length > 0) { $items.eq(0).addClass('active'); } break; case 38: // Up arrow e.preventDefault(); if (index > 0) { $items.removeClass('active'); $items.eq(index - 1).addClass('active'); } break; case 13: // Enter if ($active.length) { e.preventDefault(); window.location.href = $active.data('url'); } break; case 27: // Escape $resultsDropdown.hide(); break; } } /** * Show loading state */ function showLoading() { $resultsDropdown.html( '
' + ' ' + wclpAdmin.strings.searching + '
' ).show(); } /** * Perform AJAX search */ function performSearch(query) { if (isSearching) { return; } isSearching = true; $.ajax({ url: wclpAdmin.ajaxUrl, type: 'GET', data: { action: 'wclp_live_search', nonce: wclpAdmin.nonce, search: query }, success: function(response) { if (response.success && response.data.results) { renderResults(response.data.results, query); } else { showNoResults(); } }, error: function() { $resultsDropdown.html( '
' + wclpAdmin.strings.error + '
' ).show(); }, complete: function() { isSearching = false; } }); } /** * Render search results */ function renderResults(results, query) { if (results.length === 0) { showNoResults(); return; } var html = ''; results.forEach(function(item) { var statusClass = 'license-status-' + item.status; var highlightedKey = highlightMatch(item.license_key, query); var highlightedDomain = highlightMatch(item.domain, query); html += '
' + '
' + '' + highlightedKey + '' + '' + escapeHtml(item.status) + '' + '
' + '
' + '' + highlightedDomain + '' + '' + escapeHtml(item.product_name) + '' + '
' + '
' + escapeHtml(item.customer_name) + (item.customer_email ? ' (' + escapeHtml(item.customer_email) + ')' : '') + '
' + '
'; }); $resultsDropdown.html(html).show(); // Make items clickable $resultsDropdown.find('.wclp-search-result-item').on('click', function() { window.location.href = $(this).data('url'); }).on('mouseenter', function() { $resultsDropdown.find('.wclp-search-result-item').removeClass('active'); $(this).addClass('active'); }); } /** * Show no results message */ function showNoResults() { $resultsDropdown.html( '
' + wclpAdmin.strings.noResults + '
' ).show(); } /** * Highlight matching text */ function highlightMatch(text, query) { if (!text || !query) { return escapeHtml(text || ''); } var escaped = escapeHtml(text); var queryEscaped = escapeHtml(query); var regex = new RegExp('(' + queryEscaped.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'gi'); return escaped.replace(regex, '$1'); } /** * Escape HTML entities */ function escapeHtml(text) { if (!text) return ''; var div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Initialize when document is ready $(document).ready(init); // ======================================== // Inline Editing Functionality // ======================================== /** * Initialize inline editing */ function initInlineEditing() { // Edit button click $(document).on('click', '.wclp-edit-btn', function(e) { e.preventDefault(); var $cell = $(this).closest('.wclp-editable-cell'); showEditForm($cell); }); // Cancel button click $(document).on('click', '.wclp-cancel-btn', function(e) { e.preventDefault(); var $cell = $(this).closest('.wclp-editable-cell'); hideEditForm($cell); }); // Save button click $(document).on('click', '.wclp-save-btn', function(e) { e.preventDefault(); var $cell = $(this).closest('.wclp-editable-cell'); saveEdit($cell); }); // Lifetime button click (for expiry field) $(document).on('click', '.wclp-lifetime-btn', function(e) { e.preventDefault(); var $cell = $(this).closest('.wclp-editable-cell'); $cell.find('.wclp-edit-input').val(''); saveEdit($cell); }); // Enter key saves, Escape cancels $(document).on('keydown', '.wclp-edit-input', function(e) { var $cell = $(this).closest('.wclp-editable-cell'); if (e.keyCode === 13) { // Enter e.preventDefault(); saveEdit($cell); } else if (e.keyCode === 27) { // Escape e.preventDefault(); hideEditForm($cell); } }); } /** * Show edit form for a cell */ function showEditForm($cell) { $cell.find('.wclp-display-value, .wclp-edit-btn').hide(); $cell.find('.wclp-edit-form').show(); $cell.find('.wclp-edit-input').focus().select(); } /** * Hide edit form for a cell */ function hideEditForm($cell) { $cell.find('.wclp-edit-form').hide(); $cell.find('.wclp-display-value, .wclp-edit-btn').show(); } /** * Save edit via AJAX */ function saveEdit($cell) { var field = $cell.data('field'); var licenseId = $cell.data('license-id'); var $input = $cell.find('.wclp-edit-input'); var value = $input.val(); var $saveBtn = $cell.find('.wclp-save-btn'); var originalText = $saveBtn.text(); // Determine action based on field var action; var data = { action: '', nonce: wclpAdmin.editNonce, license_id: licenseId }; switch (field) { case 'status': data.action = 'wclp_update_license_status'; data.status = value; break; case 'expiry': data.action = 'wclp_update_license_expiry'; data.expiry_date = value; break; case 'domain': data.action = 'wclp_update_license_domain'; data.domain = value; break; default: return; } // Show saving state $saveBtn.text(wclpAdmin.strings.saving).prop('disabled', true); $cell.find('.wclp-cancel-btn, .wclp-lifetime-btn').prop('disabled', true); $.ajax({ url: wclpAdmin.ajaxUrl, type: 'POST', data: data, success: function(response) { if (response.success) { // Update display value updateDisplayValue($cell, field, response.data); hideEditForm($cell); showNotice('success', response.data.message); } else { showNotice('error', response.data.message || wclpAdmin.strings.saveFailed); } }, error: function() { showNotice('error', wclpAdmin.strings.saveFailed); }, complete: function() { $saveBtn.text(originalText).prop('disabled', false); $cell.find('.wclp-cancel-btn, .wclp-lifetime-btn').prop('disabled', false); } }); } /** * Update the display value after successful save */ function updateDisplayValue($cell, field, data) { var $display = $cell.find('.wclp-display-value'); switch (field) { case 'status': var statusHtml = '' + escapeHtml(data.status_label) + ''; $display.html(statusHtml); break; case 'expiry': if (data.expiry_date) { $display.html(escapeHtml(data.expiry_display)); } else { $display.html('' + wclpAdmin.strings.lifetime + ''); } // Update the input value $cell.find('.wclp-edit-input').val(data.expiry_date || ''); break; case 'domain': $display.text(data.domain); // Update the input value $cell.find('.wclp-edit-input').val(data.domain); break; } } /** * Show a temporary notice */ function showNotice(type, message) { var $notice = $('

' + escapeHtml(message) + '

'); // Insert at the top of the wrap $('.wrap h1').first().after($notice); // Auto-dismiss after 3 seconds setTimeout(function() { $notice.fadeOut(300, function() { $(this).remove(); }); }, 3000); } // Initialize inline editing when document is ready $(document).ready(initInlineEditing); // ======================================== // Copy License Key Functionality // ======================================== /** * Initialize copy license key buttons */ function initCopyButtons() { $(document).on('click', '.wclp-copy-btn', function(e) { e.preventDefault(); var $btn = $(this); var licenseKey = $btn.data('license-key'); copyToClipboard(licenseKey).then(function() { showCopyFeedback($btn, true); }).catch(function() { showCopyFeedback($btn, false); }); }); } /** * Copy text to clipboard */ function copyToClipboard(text) { // Modern Clipboard API if (navigator.clipboard && navigator.clipboard.writeText) { return navigator.clipboard.writeText(text); } // Fallback for older browsers return new Promise(function(resolve, reject) { var textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; document.body.appendChild(textarea); textarea.select(); try { var successful = document.execCommand('copy'); document.body.removeChild(textarea); if (successful) { resolve(); } else { reject(); } } catch (err) { document.body.removeChild(textarea); reject(err); } }); } /** * Show copy feedback */ function showCopyFeedback($btn, success) { var $icon = $btn.find('.dashicons'); var originalClass = 'dashicons-clipboard'; var feedbackClass = success ? 'dashicons-yes' : 'dashicons-no'; var feedbackColor = success ? '#00a32a' : '#d63638'; // Change icon temporarily $icon.removeClass(originalClass).addClass(feedbackClass).css('color', feedbackColor); // Show tooltip var message = success ? wclpAdmin.strings.copied : wclpAdmin.strings.copyFailed; var $tooltip = $('' + escapeHtml(message) + ''); $btn.append($tooltip); // Reset after delay setTimeout(function() { $icon.removeClass(feedbackClass).addClass(originalClass).css('color', ''); $tooltip.remove(); }, 1500); } // Initialize copy buttons when document is ready $(document).ready(initCopyButtons); })(jQuery);