/** * Maple Local Fonts - Admin JavaScript * * @package Maple_Local_Fonts */ (function($) { 'use strict'; var MLF = { /** * Search debounce timer. */ searchTimer: null, /** * Currently selected font. */ selectedFont: null, /** * Currently selected font version info. */ selectedFontVersion: null, selectedFontLastModified: null, /** * Loaded font preview stylesheets. */ loadedFonts: {}, /** * Initialize the admin functionality. */ init: function() { this.bindEvents(); }, /** * Bind event handlers. */ bindEvents: function() { // Search button click $('#mlf-search-btn').on('click', this.handleSearchClick.bind(this)); // Search on Enter key $('#mlf-font-search').on('keypress', this.handleSearchKeypress.bind(this)); // Click outside to close search results $(document).on('click', this.handleDocumentClick.bind(this)); // Prevent closing when clicking inside search area (but not on result items) $('.mlf-import-section').on('click', function(e) { // Don't stop propagation if clicking on a result item if (!$(e.target).closest('.mlf-result-item').length) { e.stopPropagation(); } }); // Font selection from results - bind to results container $('#mlf-search-results').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)); // Check for updates button $('#mlf-check-updates').on('click', this.handleCheckUpdates.bind(this)); // Update button clicks $(document).on('click', '.mlf-update-btn', this.handleUpdateFont.bind(this)); }, /** * Handle search button click. * * @param {Event} e Click event. */ handleSearchClick: function(e) { e.preventDefault(); this.triggerSearch(); }, /** * Handle Enter key in search input. * * @param {Event} e Keypress event. */ handleSearchKeypress: function(e) { if (e.which === 13) { e.preventDefault(); this.triggerSearch(); } }, /** * Trigger a search with the current input value. */ triggerSearch: function() { var query = $('#mlf-font-search').val().trim(); if (query.length < 2) { this.displayError(mapleLocalFontsData.strings.minChars || 'Please enter at least 2 characters.'); return; } this.performSearch(query); }, /** * Perform the search. * * @param {string} query Search query. */ performSearch: function(query) { var $spinner = $('#mlf-search-spinner'); var $button = $('#mlf-search-btn'); // Show spinner, disable button $spinner.addClass('is-active'); $button.prop('disabled', true); // 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 && response.data.fonts) { if (response.data.fonts.length > 0) { MLF.displaySearchResults(response.data.fonts); } else { MLF.displayNoResults(); } } else { // Handle error response var errorMsg = (response.data && response.data.message) ? response.data.message : (mapleLocalFontsData.strings.error || 'An error occurred.'); MLF.displayError(errorMsg); } }, error: function(xhr) { var errorMsg = mapleLocalFontsData.strings.error || 'An error occurred.'; if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) { errorMsg = xhr.responseJSON.data.message; } MLF.displayError(errorMsg); }, complete: function() { $spinner.removeClass('is-active'); $button.prop('disabled', false); } }); }, /** * 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 || fonts.length === 0) { this.displayNoResults(); return; } var html = ''; var previewText = mapleLocalFontsData.strings.previewText || 'Maple Fonts Preview'; var validFonts = 0; fonts.forEach(function(font) { // Skip fonts with missing family name if (!font || !font.family) { return; } 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); validFonts++; }); // If no valid fonts were found, show no results if (validFonts === 0) { this.displayNoResults(); return; } $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. Try a different search term.'; $list.html('
' + MLF.escapeHtml(message) + '
'); $results.show(); }, /** * Display an error message in search results. * * @param {string} message Error message. */ displayError: function(message) { var $results = $('#mlf-search-results'); var $list = $('#mlf-results-list'); $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 (spaces become %20 which Google accepts) var fontUrl = 'https://fonts.googleapis.com/css2?family=' + encodeURIComponent(fontFamily) + ':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 and version info this.selectedFont = fontFamily; this.selectedFontVersion = $item.data('font-version') || ''; this.selectedFontLastModified = $item.data('font-modified') || ''; // 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, font_version: this.selectedFontVersion || '', font_last_modified: this.selectedFontLastModified || '' }, 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(); }, /** * Handle check for updates button click. * * @param {Event} e Click event. */ handleCheckUpdates: function(e) { e.preventDefault(); var $button = $('#mlf-check-updates'); var originalText = $button.text(); // Disable button and show checking state $button.prop('disabled', true).text(mapleLocalFontsData.strings.checking || 'Checking...'); // Send AJAX request $.ajax({ url: mapleLocalFontsData.ajaxUrl, type: 'POST', data: { action: 'mlf_check_updates', nonce: mapleLocalFontsData.checkUpdatesNonce }, success: function(response) { if (response.success) { var updates = response.data.updates || {}; var updateCount = Object.keys(updates).length; // Update UI for each font $('.mlf-font-item').each(function() { var $item = $(this); var fontId = $item.data('font-id'); var $badge = $item.find('.mlf-update-badge'); var $updateBtn = $item.find('.mlf-update-btn'); if (updates[fontId]) { // Show update available badge and button $badge.show(); $updateBtn.show(); // Store the latest version info on the button $updateBtn.data('latest-version', updates[fontId].latest_version); } else { // Hide update badge and button $badge.hide(); $updateBtn.hide(); } }); // Show summary message if (updateCount > 0) { var message = mapleLocalFontsData.strings.updatesFound || 'Updates available for %d font(s).'; alert(message.replace('%d', updateCount)); } else { alert(mapleLocalFontsData.strings.noUpdates || 'All fonts are up to date.'); } } else { alert(response.data.message || mapleLocalFontsData.strings.error); } }, 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); }, complete: function() { $button.prop('disabled', false).text(originalText); } }); }, /** * Handle font update button click. * * @param {Event} e Click event. */ handleUpdateFont: function(e) { e.preventDefault(); var $button = $(e.currentTarget); var fontId = $button.data('font-id'); var fontName = $button.data('font-name'); var $fontItem = $button.closest('.mlf-font-item'); // Confirm update if (!confirm('Update ' + fontName + ' to the latest version?')) { return; } // Store original button text var originalText = $button.text(); // Disable button and show updating state $button.prop('disabled', true).text(mapleLocalFontsData.strings.updating || 'Updating...'); $fontItem.addClass('mlf-loading'); // Send AJAX request $.ajax({ url: mapleLocalFontsData.ajaxUrl, type: 'POST', data: { action: 'mlf_update_font', nonce: mapleLocalFontsData.updateFontNonce, font_id: fontId }, success: function(response) { if (response.success) { // Reload page to show updated font alert(response.data.message); window.location.reload(); } 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'); } }); } }; // Initialize on document ready $(document).ready(function() { MLF.init(); }); })(jQuery);