/** * Maple Local Fonts - Admin JavaScript * * @package Maple_Local_Fonts */ (function($) { 'use strict'; var MLF = { /** * Search debounce timer. */ searchTimer: null, /** * Currently selected font. */ selectedFont: null, /** * Loaded font preview stylesheets. */ loadedFonts: {}, /** * Initialize the admin functionality. */ init: function() { this.bindEvents(); }, /** * Bind event handlers. */ bindEvents: function() { // Search input $('#mlf-font-search').on('input', this.handleSearchInput.bind(this)); // Click outside to close search results $(document).on('click', this.handleDocumentClick.bind(this)); // Prevent closing when clicking inside search area $('.mlf-import-section').on('click', function(e) { e.stopPropagation(); }); // Font selection from results $(document).on('click', '.mlf-result-item', this.handleFontSelect.bind(this)); // Change font button $('#mlf-change-font').on('click', this.handleChangeFont.bind(this)); // Form submission $('#mlf-import-form').on('submit', this.handleDownload.bind(this)); // Delete button clicks $(document).on('click', '.mlf-delete-btn', this.handleDelete.bind(this)); }, /** * Handle search input (debounced). * * @param {Event} e Input event. */ handleSearchInput: function(e) { var query = $(e.target).val().trim(); // Clear previous timer if (this.searchTimer) { clearTimeout(this.searchTimer); } // Hide results if query too short if (query.length < 2) { this.hideSearchResults(); return; } // Debounce: wait 300ms before searching this.searchTimer = setTimeout(function() { MLF.performSearch(query); }, 300); }, /** * Perform the search. * * @param {string} query Search query. */ performSearch: function(query) { var $spinner = $('#mlf-search-spinner'); var $results = $('#mlf-search-results'); var $list = $('#mlf-results-list'); // Show spinner $spinner.addClass('is-active'); // Send AJAX request $.ajax({ url: mapleLocalFontsData.ajaxUrl, type: 'POST', data: { action: 'mlf_search_fonts', nonce: mapleLocalFontsData.searchNonce, query: query }, success: function(response) { if (response.success && response.data.fonts) { MLF.displaySearchResults(response.data.fonts); } else { MLF.displayNoResults(); } }, error: function() { MLF.displayNoResults(); }, complete: function() { $spinner.removeClass('is-active'); } }); }, /** * Display search results. * * @param {Array} fonts Array of font objects. */ displaySearchResults: function(fonts) { var $results = $('#mlf-search-results'); var $list = $('#mlf-results-list'); if (fonts.length === 0) { this.displayNoResults(); return; } var html = ''; var previewText = mapleLocalFontsData.strings.previewText || 'Maple Fonts Preview'; fonts.forEach(function(font) { var fontFamily = font.family; var category = font.category || 'sans-serif'; var categoryLabel = MLF.getCategoryLabel(category); var badges = []; if (font.has_variable) { badges.push('Variable'); } html += '
'; html += '
'; html += ' ' + MLF.escapeHtml(fontFamily) + ''; html += ' ' + MLF.escapeHtml(categoryLabel) + ''; if (badges.length > 0) { html += ' ' + badges.join('') + ''; } html += '
'; html += '
'; html += MLF.escapeHtml(previewText); html += '
'; html += '
'; // Load font for preview MLF.loadFontPreview(fontFamily); }); $list.html(html); $results.show(); }, /** * Display no results message. */ displayNoResults: function() { var $results = $('#mlf-search-results'); var $list = $('#mlf-results-list'); var message = mapleLocalFontsData.strings.noResults || 'No fonts found.'; $list.html('
' + MLF.escapeHtml(message) + '
'); $results.show(); }, /** * Hide search results. */ hideSearchResults: function() { $('#mlf-search-results').hide(); }, /** * Maximum number of font previews to load. */ maxLoadedFonts: 50, /** * Load a font for preview from Google Fonts. * * @param {string} fontFamily Font family name. */ loadFontPreview: function(fontFamily) { // Skip if already loaded if (this.loadedFonts[fontFamily]) { return; } // Limit number of loaded fonts to prevent memory accumulation if (Object.keys(this.loadedFonts).length >= this.maxLoadedFonts) { return; } // Mark as loading this.loadedFonts[fontFamily] = true; // Create Google Fonts link var fontUrl = 'https://fonts.googleapis.com/css2?family=' + encodeURIComponent(fontFamily.replace(/ /g, '+')) + ':wght@400&display=swap'; // Create and append link element var $link = $('', { rel: 'stylesheet', href: fontUrl }); $('head').append($link); }, /** * Handle font selection. * * @param {Event} e Click event. */ handleFontSelect: function(e) { var $item = $(e.currentTarget); var fontFamily = $item.data('font-family'); // Store selected font this.selectedFont = fontFamily; // Update UI $('#mlf-font-name').val(fontFamily); $('#mlf-selected-name').text(fontFamily); // Hide search, show selected font panel this.hideSearchResults(); $('#mlf-font-search').closest('.mlf-form-row').hide(); $('#mlf-selected-font').show(); // Clear search input $('#mlf-font-search').val(''); }, /** * Handle change font button. * * @param {Event} e Click event. */ handleChangeFont: function(e) { e.preventDefault(); // Clear selection this.selectedFont = null; $('#mlf-font-name').val(''); // Show search, hide selected font panel $('#mlf-selected-font').hide(); $('#mlf-font-search').closest('.mlf-form-row').show(); $('#mlf-font-search').focus(); }, /** * Handle document click (close search results). * * @param {Event} e Click event. */ handleDocumentClick: function(e) { if (!$(e.target).closest('.mlf-import-section').length) { this.hideSearchResults(); } }, /** * Get human-readable category label. * * @param {string} category Category slug. * @return {string} Category label. */ getCategoryLabel: function(category) { var labels = { 'sans-serif': 'Sans Serif', 'serif': 'Serif', 'display': 'Display', 'handwriting': 'Handwriting', 'monospace': 'Monospace' }; return labels[category] || category; }, /** * Escape HTML entities. * * @param {string} str String to escape. * @return {string} Escaped string. */ escapeHtml: function(str) { var div = document.createElement('div'); div.textContent = str; return div.innerHTML; }, /** * Handle font download form submission. * * @param {Event} e Form submit event. */ handleDownload: function(e) { e.preventDefault(); var $form = $('#mlf-import-form'); var $button = $('#mlf-download-btn'); var $spinner = $('#mlf-spinner'); var $message = $('#mlf-message'); // Get form values var fontName = $('#mlf-font-name').val().trim(); var includeItalic = $('#mlf-include-italic').is(':checked') ? '1' : '0'; // Validate if (!fontName) { this.showMessage($message, mapleLocalFontsData.strings.selectFont || 'Please select a font.', 'error'); return; } // Store original button text if (!$button.data('original-text')) { $button.data('original-text', $button.text()); } // Disable form $form.addClass('mlf-loading'); $button.prop('disabled', true).text(mapleLocalFontsData.strings.downloading); $spinner.addClass('is-active'); $message.hide(); // Send AJAX request $.ajax({ url: mapleLocalFontsData.ajaxUrl, type: 'POST', data: { action: 'mlf_download_font', nonce: mapleLocalFontsData.downloadNonce, font_name: fontName, include_italic: includeItalic }, success: function(response) { if (response.success) { MLF.showMessage($message, response.data.message, 'success'); // Reload page to show new font setTimeout(function() { window.location.reload(); }, 1500); } else { MLF.showMessage($message, response.data.message || mapleLocalFontsData.strings.error, 'error'); } }, error: function(xhr) { var message = mapleLocalFontsData.strings.error; if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) { message = xhr.responseJSON.data.message; } MLF.showMessage($message, message, 'error'); }, complete: function() { $form.removeClass('mlf-loading'); $button.prop('disabled', false).text($button.data('original-text')); $spinner.removeClass('is-active'); } }); }, /** * Handle font deletion. * * @param {Event} e Click event. */ handleDelete: function(e) { e.preventDefault(); var $button = $(e.currentTarget); var fontId = $button.data('font-id'); var $fontItem = $button.closest('.mlf-font-item'); // Confirm deletion if (!confirm(mapleLocalFontsData.strings.confirmDelete)) { return; } // Store original button text var originalText = $button.text(); // Disable button $button.prop('disabled', true).text(mapleLocalFontsData.strings.deleting); $fontItem.addClass('mlf-loading'); // Send AJAX request $.ajax({ url: mapleLocalFontsData.ajaxUrl, type: 'POST', data: { action: 'mlf_delete_font', nonce: mapleLocalFontsData.deleteNonce, font_id: fontId }, success: function(response) { if (response.success) { // Remove font item with animation $fontItem.slideUp(300, function() { $(this).remove(); // Check if any fonts remain if ($('.mlf-font-item').length === 0) { $('#mlf-font-list').replaceWith( '

No fonts installed yet. Search and select a font above to get started.

' ); } }); } else { alert(response.data.message || mapleLocalFontsData.strings.error); $button.prop('disabled', false).text(originalText); $fontItem.removeClass('mlf-loading'); } }, error: function(xhr) { var message = mapleLocalFontsData.strings.error; if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) { message = xhr.responseJSON.data.message; } alert(message); $button.prop('disabled', false).text(originalText); $fontItem.removeClass('mlf-loading'); } }); }, /** * Show a message to the user. * * @param {jQuery} $element Message element. * @param {string} message Message text. * @param {string} type Message type (success or error). */ showMessage: function($element, message, type) { $element .removeClass('mlf-message-success mlf-message-error') .addClass('mlf-message-' + type) .text(message) .show(); } }; // Initialize on document ready $(document).ready(function() { MLF.init(); }); })(jQuery);