font management fixes
This commit is contained in:
parent
847ed92c23
commit
d5ecb31dad
8 changed files with 728 additions and 99 deletions
|
|
@ -71,33 +71,26 @@
|
||||||
|
|
||||||
/* Search Input */
|
/* Search Input */
|
||||||
.mlf-search-wrapper {
|
.mlf-search-wrapper {
|
||||||
position: relative;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mlf-search-icon {
|
.mlf-search-input {
|
||||||
position: absolute;
|
flex: 1;
|
||||||
left: 10px;
|
padding: 6px 12px !important;
|
||||||
top: 50%;
|
font-size: 14px;
|
||||||
transform: translateY(-50%);
|
|
||||||
color: #646970;
|
|
||||||
font-size: 18px;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mlf-search-input {
|
.mlf-search-btn {
|
||||||
width: 100% !important;
|
flex-shrink: 0;
|
||||||
max-width: none !important;
|
height: 36px;
|
||||||
padding-left: 36px !important;
|
padding: 0 16px !important;
|
||||||
padding-right: 36px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mlf-search-spinner {
|
.mlf-search-spinner {
|
||||||
position: absolute;
|
flex-shrink: 0;
|
||||||
right: 8px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
float: none !important;
|
float: none !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
@ -183,6 +176,14 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mlf-search-error {
|
||||||
|
padding: 16px 24px;
|
||||||
|
text-align: center;
|
||||||
|
color: #8b6914;
|
||||||
|
background: #fcf9e8;
|
||||||
|
border-bottom: 1px solid #f0e6c8;
|
||||||
|
}
|
||||||
|
|
||||||
/* Selected Font */
|
/* Selected Font */
|
||||||
.mlf-selected-font {
|
.mlf-selected-font {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
|
|
@ -313,6 +314,26 @@
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Section Header */
|
||||||
|
.mlf-section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mlf-section-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mlf-check-updates-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Font List */
|
/* Font List */
|
||||||
.mlf-font-list {
|
.mlf-font-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -334,17 +355,61 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mlf-font-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.mlf-font-name {
|
.mlf-font-name {
|
||||||
margin: 0 0 4px 0;
|
margin: 0;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mlf-update-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: #fcf9e8;
|
||||||
|
border: 1px solid #d4b106;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #8b6914;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.mlf-font-variants {
|
.mlf-font-variants {
|
||||||
margin: 0;
|
margin: 0 0 4px 0;
|
||||||
color: #646970;
|
color: #646970;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mlf-font-meta {
|
||||||
|
margin: 0;
|
||||||
|
color: #8c8f94;
|
||||||
|
font-size: 0.8em;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mlf-font-version {
|
||||||
|
font-family: monospace;
|
||||||
|
background: #f0f0f1;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mlf-update-btn {
|
||||||
|
color: #2271b1;
|
||||||
|
border-color: #2271b1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mlf-update-btn:hover {
|
||||||
|
background: #2271b1;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #2271b1;
|
||||||
|
}
|
||||||
|
|
||||||
.mlf-font-actions {
|
.mlf-font-actions {
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,12 @@
|
||||||
*/
|
*/
|
||||||
selectedFont: null,
|
selectedFont: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently selected font version info.
|
||||||
|
*/
|
||||||
|
selectedFontVersion: null,
|
||||||
|
selectedFontLastModified: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loaded font preview stylesheets.
|
* Loaded font preview stylesheets.
|
||||||
*/
|
*/
|
||||||
|
|
@ -34,19 +40,25 @@
|
||||||
* Bind event handlers.
|
* Bind event handlers.
|
||||||
*/
|
*/
|
||||||
bindEvents: function() {
|
bindEvents: function() {
|
||||||
// Search input
|
// Search button click
|
||||||
$('#mlf-font-search').on('input', this.handleSearchInput.bind(this));
|
$('#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
|
// Click outside to close search results
|
||||||
$(document).on('click', this.handleDocumentClick.bind(this));
|
$(document).on('click', this.handleDocumentClick.bind(this));
|
||||||
|
|
||||||
// Prevent closing when clicking inside search area
|
// Prevent closing when clicking inside search area (but not on result items)
|
||||||
$('.mlf-import-section').on('click', function(e) {
|
$('.mlf-import-section').on('click', function(e) {
|
||||||
e.stopPropagation();
|
// Don't stop propagation if clicking on a result item
|
||||||
|
if (!$(e.target).closest('.mlf-result-item').length) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Font selection from results
|
// Font selection from results - bind to results container
|
||||||
$(document).on('click', '.mlf-result-item', this.handleFontSelect.bind(this));
|
$('#mlf-search-results').on('click', '.mlf-result-item', this.handleFontSelect.bind(this));
|
||||||
|
|
||||||
// Change font button
|
// Change font button
|
||||||
$('#mlf-change-font').on('click', this.handleChangeFont.bind(this));
|
$('#mlf-change-font').on('click', this.handleChangeFont.bind(this));
|
||||||
|
|
@ -56,31 +68,48 @@
|
||||||
|
|
||||||
// Delete button clicks
|
// Delete button clicks
|
||||||
$(document).on('click', '.mlf-delete-btn', this.handleDelete.bind(this));
|
$(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 input (debounced).
|
* Handle search button click.
|
||||||
*
|
*
|
||||||
* @param {Event} e Input event.
|
* @param {Event} e Click event.
|
||||||
*/
|
*/
|
||||||
handleSearchInput: function(e) {
|
handleSearchClick: function(e) {
|
||||||
var query = $(e.target).val().trim();
|
e.preventDefault();
|
||||||
|
this.triggerSearch();
|
||||||
|
},
|
||||||
|
|
||||||
// Clear previous timer
|
/**
|
||||||
if (this.searchTimer) {
|
* Handle Enter key in search input.
|
||||||
clearTimeout(this.searchTimer);
|
*
|
||||||
|
* @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();
|
||||||
|
|
||||||
// Hide results if query too short
|
|
||||||
if (query.length < 2) {
|
if (query.length < 2) {
|
||||||
this.hideSearchResults();
|
this.displayError(mapleLocalFontsData.strings.minChars || 'Please enter at least 2 characters.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debounce: wait 300ms before searching
|
this.performSearch(query);
|
||||||
this.searchTimer = setTimeout(function() {
|
|
||||||
MLF.performSearch(query);
|
|
||||||
}, 300);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -90,11 +119,11 @@
|
||||||
*/
|
*/
|
||||||
performSearch: function(query) {
|
performSearch: function(query) {
|
||||||
var $spinner = $('#mlf-search-spinner');
|
var $spinner = $('#mlf-search-spinner');
|
||||||
var $results = $('#mlf-search-results');
|
var $button = $('#mlf-search-btn');
|
||||||
var $list = $('#mlf-results-list');
|
|
||||||
|
|
||||||
// Show spinner
|
// Show spinner, disable button
|
||||||
$spinner.addClass('is-active');
|
$spinner.addClass('is-active');
|
||||||
|
$button.prop('disabled', true);
|
||||||
|
|
||||||
// Send AJAX request
|
// Send AJAX request
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
|
@ -106,17 +135,30 @@
|
||||||
query: query
|
query: query
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success && response.data.fonts) {
|
if (response.success && response.data && response.data.fonts) {
|
||||||
MLF.displaySearchResults(response.data.fonts);
|
if (response.data.fonts.length > 0) {
|
||||||
|
MLF.displaySearchResults(response.data.fonts);
|
||||||
|
} else {
|
||||||
|
MLF.displayNoResults();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
MLF.displayNoResults();
|
// Handle error response
|
||||||
|
var errorMsg = (response.data && response.data.message)
|
||||||
|
? response.data.message
|
||||||
|
: (mapleLocalFontsData.strings.error || 'An error occurred.');
|
||||||
|
MLF.displayError(errorMsg);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function(xhr) {
|
||||||
MLF.displayNoResults();
|
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() {
|
complete: function() {
|
||||||
$spinner.removeClass('is-active');
|
$spinner.removeClass('is-active');
|
||||||
|
$button.prop('disabled', false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -130,15 +172,21 @@
|
||||||
var $results = $('#mlf-search-results');
|
var $results = $('#mlf-search-results');
|
||||||
var $list = $('#mlf-results-list');
|
var $list = $('#mlf-results-list');
|
||||||
|
|
||||||
if (fonts.length === 0) {
|
if (!fonts || fonts.length === 0) {
|
||||||
this.displayNoResults();
|
this.displayNoResults();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var html = '';
|
var html = '';
|
||||||
var previewText = mapleLocalFontsData.strings.previewText || 'Maple Fonts Preview';
|
var previewText = mapleLocalFontsData.strings.previewText || 'Maple Fonts Preview';
|
||||||
|
var validFonts = 0;
|
||||||
|
|
||||||
fonts.forEach(function(font) {
|
fonts.forEach(function(font) {
|
||||||
|
// Skip fonts with missing family name
|
||||||
|
if (!font || !font.family) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var fontFamily = font.family;
|
var fontFamily = font.family;
|
||||||
var category = font.category || 'sans-serif';
|
var category = font.category || 'sans-serif';
|
||||||
var categoryLabel = MLF.getCategoryLabel(category);
|
var categoryLabel = MLF.getCategoryLabel(category);
|
||||||
|
|
@ -148,7 +196,7 @@
|
||||||
badges.push('<span class="mlf-badge mlf-badge-variable">Variable</span>');
|
badges.push('<span class="mlf-badge mlf-badge-variable">Variable</span>');
|
||||||
}
|
}
|
||||||
|
|
||||||
html += '<div class="mlf-result-item" data-font-family="' + MLF.escapeHtml(fontFamily) + '">';
|
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 += ' <div class="mlf-result-info">';
|
||||||
html += ' <span class="mlf-result-name">' + MLF.escapeHtml(fontFamily) + '</span>';
|
html += ' <span class="mlf-result-name">' + MLF.escapeHtml(fontFamily) + '</span>';
|
||||||
html += ' <span class="mlf-result-category">' + MLF.escapeHtml(categoryLabel) + '</span>';
|
html += ' <span class="mlf-result-category">' + MLF.escapeHtml(categoryLabel) + '</span>';
|
||||||
|
|
@ -163,8 +211,15 @@
|
||||||
|
|
||||||
// Load font for preview
|
// Load font for preview
|
||||||
MLF.loadFontPreview(fontFamily);
|
MLF.loadFontPreview(fontFamily);
|
||||||
|
validFonts++;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If no valid fonts were found, show no results
|
||||||
|
if (validFonts === 0) {
|
||||||
|
this.displayNoResults();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$list.html(html);
|
$list.html(html);
|
||||||
$results.show();
|
$results.show();
|
||||||
},
|
},
|
||||||
|
|
@ -175,12 +230,25 @@
|
||||||
displayNoResults: function() {
|
displayNoResults: function() {
|
||||||
var $results = $('#mlf-search-results');
|
var $results = $('#mlf-search-results');
|
||||||
var $list = $('#mlf-results-list');
|
var $list = $('#mlf-results-list');
|
||||||
var message = mapleLocalFontsData.strings.noResults || 'No fonts found.';
|
var message = mapleLocalFontsData.strings.noResults || 'No fonts found. Try a different search term.';
|
||||||
|
|
||||||
$list.html('<div class="mlf-no-results">' + MLF.escapeHtml(message) + '</div>');
|
$list.html('<div class="mlf-no-results">' + MLF.escapeHtml(message) + '</div>');
|
||||||
$results.show();
|
$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.
|
* Hide search results.
|
||||||
*/
|
*/
|
||||||
|
|
@ -212,9 +280,9 @@
|
||||||
// Mark as loading
|
// Mark as loading
|
||||||
this.loadedFonts[fontFamily] = true;
|
this.loadedFonts[fontFamily] = true;
|
||||||
|
|
||||||
// Create Google Fonts link
|
// Create Google Fonts link (spaces become %20 which Google accepts)
|
||||||
var fontUrl = 'https://fonts.googleapis.com/css2?family=' +
|
var fontUrl = 'https://fonts.googleapis.com/css2?family=' +
|
||||||
encodeURIComponent(fontFamily.replace(/ /g, '+')) +
|
encodeURIComponent(fontFamily) +
|
||||||
':wght@400&display=swap';
|
':wght@400&display=swap';
|
||||||
|
|
||||||
// Create and append link element
|
// Create and append link element
|
||||||
|
|
@ -235,8 +303,10 @@
|
||||||
var $item = $(e.currentTarget);
|
var $item = $(e.currentTarget);
|
||||||
var fontFamily = $item.data('font-family');
|
var fontFamily = $item.data('font-family');
|
||||||
|
|
||||||
// Store selected font
|
// Store selected font and version info
|
||||||
this.selectedFont = fontFamily;
|
this.selectedFont = fontFamily;
|
||||||
|
this.selectedFontVersion = $item.data('font-version') || '';
|
||||||
|
this.selectedFontLastModified = $item.data('font-modified') || '';
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
$('#mlf-font-name').val(fontFamily);
|
$('#mlf-font-name').val(fontFamily);
|
||||||
|
|
@ -351,7 +421,9 @@
|
||||||
action: 'mlf_download_font',
|
action: 'mlf_download_font',
|
||||||
nonce: mapleLocalFontsData.downloadNonce,
|
nonce: mapleLocalFontsData.downloadNonce,
|
||||||
font_name: fontName,
|
font_name: fontName,
|
||||||
include_italic: includeItalic
|
include_italic: includeItalic,
|
||||||
|
font_version: this.selectedFontVersion || '',
|
||||||
|
font_last_modified: this.selectedFontLastModified || ''
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
|
@ -456,6 +528,135 @@
|
||||||
.addClass('mlf-message-' + type)
|
.addClass('mlf-message-' + type)
|
||||||
.text(message)
|
.text(message)
|
||||||
.show();
|
.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');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,15 +49,17 @@ class MLF_Admin_Page {
|
||||||
<div class="mlf-form-row">
|
<div class="mlf-form-row">
|
||||||
<label for="mlf-font-search"><?php esc_html_e('Search Fonts', 'maple-local-fonts'); ?></label>
|
<label for="mlf-font-search"><?php esc_html_e('Search Fonts', 'maple-local-fonts'); ?></label>
|
||||||
<div class="mlf-search-wrapper">
|
<div class="mlf-search-wrapper">
|
||||||
<span class="dashicons dashicons-search mlf-search-icon"></span>
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="mlf-font-search"
|
id="mlf-font-search"
|
||||||
class="mlf-search-input"
|
class="mlf-search-input"
|
||||||
placeholder="<?php esc_attr_e('Search Google Fonts...', 'maple-local-fonts'); ?>"
|
placeholder="<?php esc_attr_e('Enter font name...', 'maple-local-fonts'); ?>"
|
||||||
autocomplete="off" />
|
autocomplete="off" />
|
||||||
|
<button type="button" class="button mlf-search-btn" id="mlf-search-btn">
|
||||||
|
<?php esc_html_e('Search', 'maple-local-fonts'); ?>
|
||||||
|
</button>
|
||||||
<span class="spinner mlf-search-spinner" id="mlf-search-spinner"></span>
|
<span class="spinner mlf-search-spinner" id="mlf-search-spinner"></span>
|
||||||
</div>
|
</div>
|
||||||
<p class="description"><?php esc_html_e('Type at least 2 characters to search.', 'maple-local-fonts'); ?></p>
|
<p class="description"><?php esc_html_e('Enter at least 2 characters and click Search.', 'maple-local-fonts'); ?></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search Results -->
|
<!-- Search Results -->
|
||||||
|
|
@ -99,22 +101,32 @@ class MLF_Admin_Page {
|
||||||
|
|
||||||
<div class="mlf-info-note">
|
<div class="mlf-info-note">
|
||||||
<span class="dashicons dashicons-info-outline"></span>
|
<span class="dashicons dashicons-info-outline"></span>
|
||||||
<span><?php esc_html_e('All font weights (100-900) will be downloaded automatically. Variable fonts are used when available for better performance.', 'maple-local-fonts'); ?></span>
|
<span><?php esc_html_e('Weights 300-900 (Light to Black) will be downloaded. Variable fonts are used when available, which include all weights in a single efficient file.', 'maple-local-fonts'); ?></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Installed Fonts Section -->
|
<!-- Installed Fonts Section -->
|
||||||
<div class="mlf-section mlf-installed-section">
|
<div class="mlf-section mlf-installed-section">
|
||||||
<h2><?php esc_html_e('Installed Fonts', 'maple-local-fonts'); ?></h2>
|
<div class="mlf-section-header">
|
||||||
|
<h2><?php esc_html_e('Installed Fonts', 'maple-local-fonts'); ?></h2>
|
||||||
|
<?php if (!empty($installed_fonts)) : ?>
|
||||||
|
<button type="button" class="button mlf-check-updates-btn" id="mlf-check-updates">
|
||||||
|
<?php esc_html_e('Check for Updates', 'maple-local-fonts'); ?>
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<?php if (empty($installed_fonts)) : ?>
|
<?php if (empty($installed_fonts)) : ?>
|
||||||
<p class="mlf-no-fonts"><?php esc_html_e('No fonts installed yet. Search and select a font above to get started.', 'maple-local-fonts'); ?></p>
|
<p class="mlf-no-fonts"><?php esc_html_e('No fonts installed yet. Search and select a font above to get started.', 'maple-local-fonts'); ?></p>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<div id="mlf-font-list" class="mlf-font-list">
|
<div id="mlf-font-list" class="mlf-font-list">
|
||||||
<?php foreach ($installed_fonts as $font) : ?>
|
<?php foreach ($installed_fonts as $font) : ?>
|
||||||
<div class="mlf-font-item" data-font-id="<?php echo esc_attr($font['id']); ?>">
|
<div class="mlf-font-item" data-font-id="<?php echo esc_attr($font['id']); ?>" data-font-name="<?php echo esc_attr($font['name']); ?>" data-font-version="<?php echo esc_attr($font['version']); ?>">
|
||||||
<div class="mlf-font-info">
|
<div class="mlf-font-info">
|
||||||
<h3 class="mlf-font-name"><?php echo esc_html($font['name']); ?></h3>
|
<div class="mlf-font-header">
|
||||||
|
<h3 class="mlf-font-name"><?php echo esc_html($font['name']); ?></h3>
|
||||||
|
<span class="mlf-update-badge" style="display: none;"><?php esc_html_e('Update available', 'maple-local-fonts'); ?></span>
|
||||||
|
</div>
|
||||||
<p class="mlf-font-variants">
|
<p class="mlf-font-variants">
|
||||||
<?php
|
<?php
|
||||||
$variant_strings = [];
|
$variant_strings = [];
|
||||||
|
|
@ -124,8 +136,22 @@ class MLF_Admin_Page {
|
||||||
echo esc_html(implode(', ', $variant_strings));
|
echo esc_html(implode(', ', $variant_strings));
|
||||||
?>
|
?>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="mlf-font-meta">
|
||||||
|
<?php if (!empty($font['version'])) : ?>
|
||||||
|
<span class="mlf-font-version"><?php echo esc_html($font['version']); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!empty($font['last_modified'])) : ?>
|
||||||
|
<span class="mlf-font-modified"><?php
|
||||||
|
/* translators: %s: date */
|
||||||
|
printf(esc_html__('Updated: %s', 'maple-local-fonts'), esc_html($font['last_modified']));
|
||||||
|
?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mlf-font-actions">
|
<div class="mlf-font-actions">
|
||||||
|
<button type="button" class="button mlf-update-btn" data-font-id="<?php echo esc_attr($font['id']); ?>" data-font-name="<?php echo esc_attr($font['name']); ?>" style="display: none;">
|
||||||
|
<?php esc_html_e('Update', 'maple-local-fonts'); ?>
|
||||||
|
</button>
|
||||||
<button type="button" class="button mlf-delete-btn" data-font-id="<?php echo esc_attr($font['id']); ?>">
|
<button type="button" class="button mlf-delete-btn" data-font-id="<?php echo esc_attr($font['id']); ?>">
|
||||||
<?php esc_html_e('Delete', 'maple-local-fonts'); ?>
|
<?php esc_html_e('Delete', 'maple-local-fonts'); ?>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ class MLF_Ajax_Handler {
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
add_action('wp_ajax_mlf_download_font', [$this, 'handle_download']);
|
add_action('wp_ajax_mlf_download_font', [$this, 'handle_download']);
|
||||||
add_action('wp_ajax_mlf_delete_font', [$this, 'handle_delete']);
|
add_action('wp_ajax_mlf_delete_font', [$this, 'handle_delete']);
|
||||||
|
add_action('wp_ajax_mlf_check_updates', [$this, 'handle_check_updates']);
|
||||||
|
add_action('wp_ajax_mlf_update_font', [$this, 'handle_update_font']);
|
||||||
// NEVER add wp_ajax_nopriv_ - admin only functionality
|
// NEVER add wp_ajax_nopriv_ - admin only functionality
|
||||||
|
|
||||||
// Initialize rate limiter: 10 requests per minute
|
// Initialize rate limiter: 10 requests per minute
|
||||||
|
|
@ -77,6 +79,10 @@ class MLF_Ajax_Handler {
|
||||||
// Validate include_italic (boolean)
|
// Validate include_italic (boolean)
|
||||||
$include_italic = isset($_POST['include_italic']) && $_POST['include_italic'] === '1';
|
$include_italic = isset($_POST['include_italic']) && $_POST['include_italic'] === '1';
|
||||||
|
|
||||||
|
// Get version info (optional)
|
||||||
|
$font_version = isset($_POST['font_version']) ? sanitize_text_field(wp_unslash($_POST['font_version'])) : '';
|
||||||
|
$font_last_modified = isset($_POST['font_last_modified']) ? sanitize_text_field(wp_unslash($_POST['font_last_modified'])) : '';
|
||||||
|
|
||||||
// 5. PROCESS REQUEST
|
// 5. PROCESS REQUEST
|
||||||
try {
|
try {
|
||||||
$downloader = new MLF_Font_Downloader();
|
$downloader = new MLF_Font_Downloader();
|
||||||
|
|
@ -91,7 +97,9 @@ class MLF_Ajax_Handler {
|
||||||
$result = $registry->register_font(
|
$result = $registry->register_font(
|
||||||
$download_result['font_name'],
|
$download_result['font_name'],
|
||||||
$download_result['font_slug'],
|
$download_result['font_slug'],
|
||||||
$download_result['files']
|
$download_result['files'],
|
||||||
|
$font_version,
|
||||||
|
$font_last_modified
|
||||||
);
|
);
|
||||||
|
|
||||||
if (is_wp_error($result)) {
|
if (is_wp_error($result)) {
|
||||||
|
|
@ -170,6 +178,166 @@ class MLF_Ajax_Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle check for updates AJAX request.
|
||||||
|
*/
|
||||||
|
public function handle_check_updates() {
|
||||||
|
// 1. NONCE CHECK
|
||||||
|
if (!check_ajax_referer('mlf_check_updates', 'nonce', false)) {
|
||||||
|
wp_send_json_error(['message' => __('Security check failed.', 'maple-local-fonts')], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. CAPABILITY CHECK
|
||||||
|
$capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
|
||||||
|
if (!current_user_can($capability)) {
|
||||||
|
wp_send_json_error(['message' => __('Unauthorized.', 'maple-local-fonts')], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. RATE LIMIT CHECK
|
||||||
|
if (!$this->rate_limiter->check_and_record('check_updates')) {
|
||||||
|
wp_send_json_error([
|
||||||
|
'message' => __('Too many requests. Please wait a moment and try again.', 'maple-local-fonts'),
|
||||||
|
], 429);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. GET INSTALLED FONTS AND CHECK VERSIONS
|
||||||
|
try {
|
||||||
|
$registry = new MLF_Font_Registry();
|
||||||
|
$installed_fonts = $registry->get_imported_fonts();
|
||||||
|
|
||||||
|
if (empty($installed_fonts)) {
|
||||||
|
wp_send_json_success(['updates' => []]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$font_search = new MLF_Font_Search();
|
||||||
|
$updates = [];
|
||||||
|
|
||||||
|
foreach ($installed_fonts as $font) {
|
||||||
|
$current_version = $font_search->get_font_version($font['name']);
|
||||||
|
|
||||||
|
if ($current_version && !empty($current_version['version'])) {
|
||||||
|
$installed_version = $font['version'] ?? '';
|
||||||
|
|
||||||
|
// Compare versions
|
||||||
|
if (!empty($installed_version) && $installed_version !== $current_version['version']) {
|
||||||
|
$updates[$font['id']] = [
|
||||||
|
'installed_version' => $installed_version,
|
||||||
|
'latest_version' => $current_version['version'],
|
||||||
|
'last_modified' => $current_version['lastModified'],
|
||||||
|
];
|
||||||
|
} elseif (empty($installed_version)) {
|
||||||
|
// No version stored, consider it needs update
|
||||||
|
$updates[$font['id']] = [
|
||||||
|
'installed_version' => '',
|
||||||
|
'latest_version' => $current_version['version'],
|
||||||
|
'last_modified' => $current_version['lastModified'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success(['updates' => $updates]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('MLF Check Updates Error: ' . sanitize_text_field($e->getMessage()));
|
||||||
|
wp_send_json_error(['message' => __('An unexpected error occurred.', 'maple-local-fonts')]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle font update (re-download) AJAX request.
|
||||||
|
*/
|
||||||
|
public function handle_update_font() {
|
||||||
|
// 1. NONCE CHECK
|
||||||
|
if (!check_ajax_referer('mlf_update_font', 'nonce', false)) {
|
||||||
|
wp_send_json_error(['message' => __('Security check failed.', 'maple-local-fonts')], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. CAPABILITY CHECK
|
||||||
|
$capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
|
||||||
|
if (!current_user_can($capability)) {
|
||||||
|
wp_send_json_error(['message' => __('Unauthorized.', 'maple-local-fonts')], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. RATE LIMIT CHECK
|
||||||
|
if (!$this->rate_limiter->check_and_record('update')) {
|
||||||
|
wp_send_json_error([
|
||||||
|
'message' => __('Too many requests. Please wait a moment and try again.', 'maple-local-fonts'),
|
||||||
|
], 429);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. INPUT VALIDATION
|
||||||
|
$font_id = isset($_POST['font_id']) ? absint($_POST['font_id']) : 0;
|
||||||
|
|
||||||
|
if ($font_id < 1) {
|
||||||
|
wp_send_json_error(['message' => __('Invalid font ID.', 'maple-local-fonts')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify font exists and is one we imported
|
||||||
|
$font = get_post($font_id);
|
||||||
|
if (!$font || $font->post_type !== 'wp_font_family') {
|
||||||
|
wp_send_json_error(['message' => __('Font not found.', 'maple-local-fonts')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_post_meta($font_id, '_mlf_imported', true) !== '1') {
|
||||||
|
wp_send_json_error(['message' => __('Cannot update fonts not imported by this plugin.', 'maple-local-fonts')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get font name from post content
|
||||||
|
$settings = json_decode($font->post_content, true);
|
||||||
|
$font_name = $settings['name'] ?? $font->post_title;
|
||||||
|
|
||||||
|
// 5. DELETE OLD FONT AND RE-DOWNLOAD
|
||||||
|
try {
|
||||||
|
$registry = new MLF_Font_Registry();
|
||||||
|
|
||||||
|
// Delete old font
|
||||||
|
$delete_result = $registry->delete_font($font_id);
|
||||||
|
if (is_wp_error($delete_result)) {
|
||||||
|
wp_send_json_error(['message' => $this->get_user_error_message($delete_result)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get latest version info
|
||||||
|
$font_search = new MLF_Font_Search();
|
||||||
|
$version_info = $font_search->get_font_version($font_name);
|
||||||
|
$font_version = $version_info['version'] ?? '';
|
||||||
|
$font_last_modified = $version_info['lastModified'] ?? '';
|
||||||
|
|
||||||
|
// Re-download font (include italic by default)
|
||||||
|
$downloader = new MLF_Font_Downloader();
|
||||||
|
$download_result = $downloader->download($font_name, true);
|
||||||
|
|
||||||
|
if (is_wp_error($download_result)) {
|
||||||
|
wp_send_json_error(['message' => $this->get_user_error_message($download_result)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register font with new version info
|
||||||
|
$result = $registry->register_font(
|
||||||
|
$download_result['font_name'],
|
||||||
|
$download_result['font_slug'],
|
||||||
|
$download_result['files'],
|
||||||
|
$font_version,
|
||||||
|
$font_last_modified
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is_wp_error($result)) {
|
||||||
|
wp_send_json_error(['message' => $this->get_user_error_message($result)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success([
|
||||||
|
'message' => sprintf(
|
||||||
|
/* translators: %s: font name */
|
||||||
|
__('Successfully updated %s.', 'maple-local-fonts'),
|
||||||
|
esc_html($font_name)
|
||||||
|
),
|
||||||
|
'font_id' => $result,
|
||||||
|
'version' => $font_version,
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('MLF Update Font Error: ' . sanitize_text_field($e->getMessage()));
|
||||||
|
wp_send_json_error(['message' => __('An unexpected error occurred.', 'maple-local-fonts')]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert internal error codes to user-friendly messages.
|
* Convert internal error codes to user-friendly messages.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,14 @@ class MLF_Font_Downloader {
|
||||||
private $user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
private $user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All available font weights.
|
* Common font weights to request.
|
||||||
|
*
|
||||||
|
* Most fonts support at least 300-700. We also include 800-900 for fonts
|
||||||
|
* that support Black/Heavy weights.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $all_weights = [100, 200, 300, 400, 500, 600, 700, 800, 900];
|
private $all_weights = [300, 400, 500, 600, 700, 800, 900];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download a font from Google Fonts.
|
* Download a font from Google Fonts.
|
||||||
|
|
@ -151,6 +154,8 @@ class MLF_Font_Downloader {
|
||||||
/**
|
/**
|
||||||
* Download static fonts (fallback when variable not available).
|
* Download static fonts (fallback when variable not available).
|
||||||
*
|
*
|
||||||
|
* Tries different weight combinations since fonts support varying weights.
|
||||||
|
*
|
||||||
* @param string $font_name Font family name.
|
* @param string $font_name Font family name.
|
||||||
* @param bool $include_italic Whether to include italic styles.
|
* @param bool $include_italic Whether to include italic styles.
|
||||||
* @return array|WP_Error Download result or error.
|
* @return array|WP_Error Download result or error.
|
||||||
|
|
@ -158,8 +163,28 @@ class MLF_Font_Downloader {
|
||||||
private function download_static_fonts($font_name, $include_italic) {
|
private function download_static_fonts($font_name, $include_italic) {
|
||||||
$styles = $include_italic ? ['normal', 'italic'] : ['normal'];
|
$styles = $include_italic ? ['normal', 'italic'] : ['normal'];
|
||||||
|
|
||||||
// Fetch CSS from Google
|
// Try different weight combinations - fonts support varying weights
|
||||||
$css = $this->fetch_static_css($font_name, $this->all_weights, $styles);
|
$weight_sets = [
|
||||||
|
[300, 400, 500, 600, 700, 800, 900], // Full range
|
||||||
|
[300, 400, 500, 600, 700, 800], // Without 900
|
||||||
|
[300, 400, 500, 600, 700], // Common range
|
||||||
|
[400, 500, 600, 700], // Without light
|
||||||
|
[400, 700], // Just regular and bold
|
||||||
|
];
|
||||||
|
|
||||||
|
$css = null;
|
||||||
|
foreach ($weight_sets as $weights) {
|
||||||
|
$css = $this->fetch_static_css($font_name, $weights, $styles);
|
||||||
|
|
||||||
|
if (!is_wp_error($css)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If error is not "font not found", stop trying
|
||||||
|
if ($css->get_error_code() !== 'font_not_found') {
|
||||||
|
return $css;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (is_wp_error($css)) {
|
if (is_wp_error($css)) {
|
||||||
return $css;
|
return $css;
|
||||||
|
|
@ -191,6 +216,9 @@ class MLF_Font_Downloader {
|
||||||
/**
|
/**
|
||||||
* Fetch variable font CSS from Google Fonts API.
|
* Fetch variable font CSS from Google Fonts API.
|
||||||
*
|
*
|
||||||
|
* Tries progressively smaller weight ranges until one works,
|
||||||
|
* since different fonts support different weight ranges.
|
||||||
|
*
|
||||||
* @param string $font_name Font family name.
|
* @param string $font_name Font family name.
|
||||||
* @param bool $italic Whether to fetch italic variant.
|
* @param bool $italic Whether to fetch italic variant.
|
||||||
* @return string|WP_Error CSS content or error.
|
* @return string|WP_Error CSS content or error.
|
||||||
|
|
@ -198,15 +226,26 @@ class MLF_Font_Downloader {
|
||||||
private function fetch_variable_css($font_name, $italic = false) {
|
private function fetch_variable_css($font_name, $italic = false) {
|
||||||
$family = str_replace(' ', '+', $font_name);
|
$family = str_replace(' ', '+', $font_name);
|
||||||
|
|
||||||
if ($italic) {
|
// Try different weight ranges - fonts support varying ranges
|
||||||
// Request italic variable font
|
$weight_ranges = ['300..900', '300..800', '300..700', '400..700'];
|
||||||
$url = "https://fonts.googleapis.com/css2?family={$family}:ital,wght@1,100..900&display=swap";
|
|
||||||
} else {
|
foreach ($weight_ranges as $range) {
|
||||||
// Request roman variable font
|
if ($italic) {
|
||||||
$url = "https://fonts.googleapis.com/css2?family={$family}:wght@100..900&display=swap";
|
$url = "https://fonts.googleapis.com/css2?family={$family}:ital,wght@1,{$range}&display=swap";
|
||||||
|
} else {
|
||||||
|
$url = "https://fonts.googleapis.com/css2?family={$family}:wght@{$range}&display=swap";
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->fetch_css($url);
|
||||||
|
|
||||||
|
// If successful or error is not "font not found", return
|
||||||
|
if (!is_wp_error($result) || $result->get_error_code() !== 'font_not_found') {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->fetch_css($url);
|
// All ranges failed
|
||||||
|
return new WP_Error('no_variable', 'Variable font not available');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,14 @@ class MLF_Font_Registry {
|
||||||
/**
|
/**
|
||||||
* Register a font family with WordPress Font Library.
|
* Register a font family with WordPress Font Library.
|
||||||
*
|
*
|
||||||
* @param string $font_name Display name (e.g., "Open Sans").
|
* @param string $font_name Display name (e.g., "Open Sans").
|
||||||
* @param string $font_slug Slug (e.g., "open-sans").
|
* @param string $font_slug Slug (e.g., "open-sans").
|
||||||
* @param array $files Array of downloaded file data.
|
* @param array $files Array of downloaded file data.
|
||||||
|
* @param string $font_version Google Fonts version (e.g., "v35").
|
||||||
|
* @param string $last_modified Google Fonts last modified date.
|
||||||
* @return int|WP_Error Font family post ID or error.
|
* @return int|WP_Error Font family post ID or error.
|
||||||
*/
|
*/
|
||||||
public function register_font($font_name, $font_slug, $files) {
|
public function register_font($font_name, $font_slug, $files, $font_version = '', $last_modified = '') {
|
||||||
// Check if font already exists
|
// Check if font already exists
|
||||||
$existing = get_posts([
|
$existing = get_posts([
|
||||||
'post_type' => 'wp_font_family',
|
'post_type' => 'wp_font_family',
|
||||||
|
|
@ -88,6 +90,14 @@ class MLF_Font_Registry {
|
||||||
update_post_meta($family_id, '_mlf_imported', '1');
|
update_post_meta($family_id, '_mlf_imported', '1');
|
||||||
update_post_meta($family_id, '_mlf_import_date', current_time('mysql'));
|
update_post_meta($family_id, '_mlf_import_date', current_time('mysql'));
|
||||||
|
|
||||||
|
// Store version info for update checking
|
||||||
|
if (!empty($font_version)) {
|
||||||
|
update_post_meta($family_id, '_mlf_font_version', $font_version);
|
||||||
|
}
|
||||||
|
if (!empty($last_modified)) {
|
||||||
|
update_post_meta($family_id, '_mlf_font_last_modified', $last_modified);
|
||||||
|
}
|
||||||
|
|
||||||
// Create font face posts (children) - WordPress also reads these
|
// Create font face posts (children) - WordPress also reads these
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
$filename = basename($file['path']);
|
$filename = basename($file['path']);
|
||||||
|
|
@ -190,6 +200,11 @@ class MLF_Font_Registry {
|
||||||
delete_transient('global_styles');
|
delete_transient('global_styles');
|
||||||
delete_transient('global_styles_' . get_stylesheet());
|
delete_transient('global_styles_' . get_stylesheet());
|
||||||
|
|
||||||
|
// Clear WP_Theme_JSON caches
|
||||||
|
if (class_exists('WP_Theme_JSON_Resolver')) {
|
||||||
|
WP_Theme_JSON_Resolver::clean_cached_data();
|
||||||
|
}
|
||||||
|
|
||||||
// Clear object cache for post queries
|
// Clear object cache for post queries
|
||||||
wp_cache_flush_group('posts');
|
wp_cache_flush_group('posts');
|
||||||
|
|
||||||
|
|
@ -248,10 +263,14 @@ class MLF_Font_Registry {
|
||||||
$faces_by_font[$parent_id][] = $face;
|
$faces_by_font[$parent_id][] = $face;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch get all import dates in single query
|
// Batch get all metadata
|
||||||
$import_dates = [];
|
$import_dates = [];
|
||||||
|
$font_versions = [];
|
||||||
|
$font_last_modified = [];
|
||||||
foreach ($font_ids as $font_id) {
|
foreach ($font_ids as $font_id) {
|
||||||
$import_dates[$font_id] = get_post_meta($font_id, '_mlf_import_date', true);
|
$import_dates[$font_id] = get_post_meta($font_id, '_mlf_import_date', true);
|
||||||
|
$font_versions[$font_id] = get_post_meta($font_id, '_mlf_font_version', true);
|
||||||
|
$font_last_modified[$font_id] = get_post_meta($font_id, '_mlf_font_last_modified', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
@ -285,11 +304,13 @@ class MLF_Font_Registry {
|
||||||
});
|
});
|
||||||
|
|
||||||
$result[] = [
|
$result[] = [
|
||||||
'id' => $font->ID,
|
'id' => $font->ID,
|
||||||
'name' => $settings['name'] ?? $font->post_title,
|
'name' => $settings['name'] ?? $font->post_title,
|
||||||
'slug' => $settings['slug'] ?? $font->post_name,
|
'slug' => $settings['slug'] ?? $font->post_name,
|
||||||
'variants' => $variants,
|
'variants' => $variants,
|
||||||
'import_date' => $import_dates[$font->ID] ?? '',
|
'import_date' => $import_dates[$font->ID] ?? '',
|
||||||
|
'version' => $font_versions[$font->ID] ?? '',
|
||||||
|
'last_modified' => $font_last_modified[$font->ID] ?? '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,13 @@ class MLF_Font_Search {
|
||||||
const METADATA_URL = 'https://fonts.google.com/metadata/fonts';
|
const METADATA_URL = 'https://fonts.google.com/metadata/fonts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum metadata response size (2MB).
|
* Maximum metadata response size (10MB).
|
||||||
|
*
|
||||||
|
* Google Fonts metadata for 1500+ fonts can be 5-8MB.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
const MAX_METADATA_SIZE = 2 * 1024 * 1024;
|
const MAX_METADATA_SIZE = 10 * 1024 * 1024;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rate limiter instance.
|
* Rate limiter instance.
|
||||||
|
|
@ -165,10 +167,12 @@ class MLF_Font_Search {
|
||||||
$fonts = [];
|
$fonts = [];
|
||||||
foreach ($data['familyMetadataList'] as $font) {
|
foreach ($data['familyMetadataList'] as $font) {
|
||||||
$fonts[] = [
|
$fonts[] = [
|
||||||
'family' => $font['family'],
|
'family' => $font['family'],
|
||||||
'category' => $font['category'] ?? 'sans-serif',
|
'category' => $font['category'] ?? 'sans-serif',
|
||||||
'variants' => $font['fonts'] ?? [],
|
'variants' => $font['fonts'] ?? [],
|
||||||
'subsets' => $font['subsets'] ?? ['latin'],
|
'subsets' => $font['subsets'] ?? ['latin'],
|
||||||
|
'version' => $font['version'] ?? '',
|
||||||
|
'lastModified' => $font['lastModified'] ?? '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,6 +257,8 @@ class MLF_Font_Search {
|
||||||
'has_variable' => $has_variable,
|
'has_variable' => $has_variable,
|
||||||
'has_italic' => $has_italic,
|
'has_italic' => $has_italic,
|
||||||
'weights' => $weights,
|
'weights' => $weights,
|
||||||
|
'version' => $font['version'] ?? '',
|
||||||
|
'lastModified' => $font['lastModified'] ?? '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,4 +268,31 @@ class MLF_Font_Search {
|
||||||
public function clear_cache() {
|
public function clear_cache() {
|
||||||
delete_transient(self::CACHE_KEY);
|
delete_transient(self::CACHE_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get version info for a specific font.
|
||||||
|
*
|
||||||
|
* @param string $font_family Font family name.
|
||||||
|
* @return array|null Version info or null if not found.
|
||||||
|
*/
|
||||||
|
public function get_font_version($font_family) {
|
||||||
|
$fonts = $this->get_fonts_metadata();
|
||||||
|
|
||||||
|
if (is_wp_error($fonts)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$font_family_lower = strtolower($font_family);
|
||||||
|
|
||||||
|
foreach ($fonts as $font) {
|
||||||
|
if (strtolower($font['family']) === $font_family_lower) {
|
||||||
|
return [
|
||||||
|
'version' => $font['version'] ?? '',
|
||||||
|
'lastModified' => $font['lastModified'] ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,72 @@ function mlf_get_capability() {
|
||||||
return apply_filters('mlf_manage_fonts_capability', 'edit_theme_options');
|
return apply_filters('mlf_manage_fonts_capability', 'edit_theme_options');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add imported fonts to the theme.json typography settings.
|
||||||
|
*
|
||||||
|
* This makes fonts appear in the Site Editor typography dropdown.
|
||||||
|
*
|
||||||
|
* @param WP_Theme_JSON_Data $theme_json The theme.json data.
|
||||||
|
* @return WP_Theme_JSON_Data Modified theme.json data.
|
||||||
|
*/
|
||||||
|
function mlf_add_fonts_to_theme_json($theme_json) {
|
||||||
|
$registry = new MLF_Font_Registry();
|
||||||
|
$fonts = $registry->get_imported_fonts();
|
||||||
|
|
||||||
|
if (empty($fonts)) {
|
||||||
|
return $theme_json;
|
||||||
|
}
|
||||||
|
|
||||||
|
$font_families = [];
|
||||||
|
|
||||||
|
foreach ($fonts as $font) {
|
||||||
|
$font_faces = [];
|
||||||
|
|
||||||
|
foreach ($font['variants'] as $variant) {
|
||||||
|
$weight = $variant['weight'];
|
||||||
|
$style = $variant['style'];
|
||||||
|
|
||||||
|
// Build filename based on our naming convention
|
||||||
|
$font_slug = sanitize_title($font['name']);
|
||||||
|
if (strpos($weight, ' ') !== false) {
|
||||||
|
// Variable font
|
||||||
|
$filename = sprintf('%s_%s_variable.woff2', $font_slug, $style);
|
||||||
|
} else {
|
||||||
|
$filename = sprintf('%s_%s_%s.woff2', $font_slug, $style, $weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
$font_dir = wp_get_font_dir();
|
||||||
|
$font_url = trailingslashit($font_dir['url']) . $filename;
|
||||||
|
|
||||||
|
$font_faces[] = [
|
||||||
|
'fontFamily' => $font['name'],
|
||||||
|
'fontWeight' => $weight,
|
||||||
|
'fontStyle' => $style,
|
||||||
|
'src' => [$font_url],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$font_families[] = [
|
||||||
|
'name' => $font['name'],
|
||||||
|
'slug' => $font['slug'],
|
||||||
|
'fontFamily' => "'{$font['name']}', sans-serif",
|
||||||
|
'fontFace' => $font_faces,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_data = [
|
||||||
|
'version' => 2,
|
||||||
|
'settings' => [
|
||||||
|
'typography' => [
|
||||||
|
'fontFamilies' => $font_families,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $theme_json->update_with($new_data);
|
||||||
|
}
|
||||||
|
add_filter('wp_theme_json_data_user', 'mlf_add_fonts_to_theme_json');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register admin menu.
|
* Register admin menu.
|
||||||
*/
|
*/
|
||||||
|
|
@ -201,36 +267,46 @@ function mlf_enqueue_admin_assets($hook) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use filemtime for cache-busting during development
|
||||||
|
$css_version = MLF_VERSION . '.' . filemtime(MLF_PLUGIN_DIR . 'assets/admin.css');
|
||||||
|
$js_version = MLF_VERSION . '.' . filemtime(MLF_PLUGIN_DIR . 'assets/admin.js');
|
||||||
|
|
||||||
wp_enqueue_style(
|
wp_enqueue_style(
|
||||||
'mlf-admin',
|
'mlf-admin',
|
||||||
MLF_PLUGIN_URL . 'assets/admin.css',
|
MLF_PLUGIN_URL . 'assets/admin.css',
|
||||||
[],
|
[],
|
||||||
MLF_VERSION
|
$css_version
|
||||||
);
|
);
|
||||||
|
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'mlf-admin',
|
'mlf-admin',
|
||||||
MLF_PLUGIN_URL . 'assets/admin.js',
|
MLF_PLUGIN_URL . 'assets/admin.js',
|
||||||
['jquery'],
|
['jquery'],
|
||||||
MLF_VERSION,
|
$js_version,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
wp_localize_script('mlf-admin', 'mapleLocalFontsData', [
|
wp_localize_script('mlf-admin', 'mapleLocalFontsData', [
|
||||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||||
'downloadNonce' => wp_create_nonce('mlf_download_font'),
|
'downloadNonce' => wp_create_nonce('mlf_download_font'),
|
||||||
'deleteNonce' => wp_create_nonce('mlf_delete_font'),
|
'deleteNonce' => wp_create_nonce('mlf_delete_font'),
|
||||||
'searchNonce' => wp_create_nonce('mlf_search_fonts'),
|
'searchNonce' => wp_create_nonce('mlf_search_fonts'),
|
||||||
'strings' => [
|
'checkUpdatesNonce' => wp_create_nonce('mlf_check_updates'),
|
||||||
|
'updateFontNonce' => wp_create_nonce('mlf_update_font'),
|
||||||
|
'strings' => [
|
||||||
'downloading' => __('Downloading...', 'maple-local-fonts'),
|
'downloading' => __('Downloading...', 'maple-local-fonts'),
|
||||||
'deleting' => __('Deleting...', 'maple-local-fonts'),
|
'deleting' => __('Deleting...', 'maple-local-fonts'),
|
||||||
|
'updating' => __('Updating...', 'maple-local-fonts'),
|
||||||
|
'checking' => __('Checking...', 'maple-local-fonts'),
|
||||||
'confirmDelete' => __('Are you sure you want to delete this font?', 'maple-local-fonts'),
|
'confirmDelete' => __('Are you sure you want to delete this font?', 'maple-local-fonts'),
|
||||||
'error' => __('An error occurred. Please try again.', 'maple-local-fonts'),
|
'error' => __('An error occurred. Please try again.', 'maple-local-fonts'),
|
||||||
'searchPlaceholder' => __('Search Google Fonts...', 'maple-local-fonts'),
|
|
||||||
'searching' => __('Searching...', 'maple-local-fonts'),
|
'searching' => __('Searching...', 'maple-local-fonts'),
|
||||||
'noResults' => __('No fonts found. Try a different search term.', 'maple-local-fonts'),
|
'noResults' => __('No fonts found. Try a different search term.', 'maple-local-fonts'),
|
||||||
'selectFont' => __('Select a font from the search results above.', 'maple-local-fonts'),
|
'selectFont' => __('Please select a font first.', 'maple-local-fonts'),
|
||||||
'previewText' => __('Maple Fonts Preview', 'maple-local-fonts'),
|
'previewText' => __('Maple Fonts Preview', 'maple-local-fonts'),
|
||||||
|
'minChars' => __('Please enter at least 2 characters.', 'maple-local-fonts'),
|
||||||
|
'noUpdates' => __('All fonts are up to date.', 'maple-local-fonts'),
|
||||||
|
'updatesFound' => __('Updates available for %d font(s).', 'maple-local-fonts'),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue