monorepo/native/wordpress/maple-fonts-wp/assets/admin.js
2026-02-02 08:31:36 -05:00

668 lines
24 KiB
JavaScript

/**
* 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('<span class="mlf-badge mlf-badge-variable">Variable</span>');
}
html += '<div class="mlf-result-item" data-font-family="' + MLF.escapeHtml(fontFamily) + '" data-font-version="' + MLF.escapeHtml(font.version || '') + '" data-font-modified="' + MLF.escapeHtml(font.lastModified || '') + '">';
html += ' <div class="mlf-result-info">';
html += ' <span class="mlf-result-name">' + MLF.escapeHtml(fontFamily) + '</span>';
html += ' <span class="mlf-result-category">' + MLF.escapeHtml(categoryLabel) + '</span>';
if (badges.length > 0) {
html += ' <span class="mlf-result-badges">' + badges.join('') + '</span>';
}
html += ' </div>';
html += ' <div class="mlf-result-preview" style="font-family: \'' + MLF.escapeHtml(fontFamily) + '\', ' + category + ';">';
html += MLF.escapeHtml(previewText);
html += ' </div>';
html += '</div>';
// 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('<div class="mlf-no-results">' + MLF.escapeHtml(message) + '</div>');
$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('<div class="mlf-search-error">' + MLF.escapeHtml(message) + '</div>');
$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 = $('<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(
'<p class="mlf-no-fonts">No fonts installed yet. Search and select a font above to get started.</p>'
);
}
});
} 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);