From 847ed92c23c01492e0103a16ebfcefdafe462dba Mon Sep 17 00:00:00 2001 From: rodolfomartinez Date: Mon, 2 Feb 2026 00:13:36 -0500 Subject: [PATCH] v1-pre --- .../wordpress/maple-fonts-wp/assets/admin.css | 299 ++++++++---- .../wordpress/maple-fonts-wp/assets/admin.js | 425 ++++++++++------ .../includes/class-mlf-admin-page.php | 146 +++--- .../includes/class-mlf-ajax-handler.php | 35 +- .../includes/class-mlf-font-downloader.php | 462 ++++++++++++------ .../includes/class-mlf-font-registry.php | 83 +++- .../includes/class-mlf-font-search.php | 265 ++++++++++ .../includes/class-mlf-rest-controller.php | 60 +-- .../maple-fonts-wp/maple-local-fonts.php | 43 +- native/wordpress/maple-fonts-wp/uninstall.php | 5 +- 10 files changed, 1232 insertions(+), 591 deletions(-) create mode 100644 native/wordpress/maple-fonts-wp/includes/class-mlf-font-search.php diff --git a/native/wordpress/maple-fonts-wp/assets/admin.css b/native/wordpress/maple-fonts-wp/assets/admin.css index 420b8b4..6858277 100644 --- a/native/wordpress/maple-fonts-wp/assets/admin.css +++ b/native/wordpress/maple-fonts-wp/assets/admin.css @@ -6,7 +6,17 @@ /* Container */ .mlf-wrap { - max-width: 900px; + max-width: 800px; +} + +.mlf-wrap > h1 { + margin-bottom: 0; +} + +.mlf-description { + color: #646970; + margin-top: 8px; + margin-bottom: 20px; } .mlf-container { @@ -18,14 +28,16 @@ background: #fff; border: 1px solid #c3c4c7; border-radius: 4px; - padding: 20px; + padding: 20px 24px; margin-bottom: 20px; } .mlf-section h2 { margin-top: 0; - padding-bottom: 10px; - border-bottom: 1px solid #c3c4c7; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid #e0e0e0; + font-size: 1.1em; } /* Form */ @@ -33,7 +45,11 @@ margin-bottom: 20px; } -.mlf-form-row label { +.mlf-form-row:last-of-type { + margin-bottom: 0; +} + +.mlf-form-row > label { display: block; font-weight: 600; margin-bottom: 8px; @@ -43,126 +59,212 @@ width: 100%; max-width: 400px; padding: 8px 12px; + font-size: 14px; } .mlf-form-row .description { margin-top: 8px; color: #646970; font-style: italic; + font-size: 13px; } -/* Checkbox Grid */ -.mlf-checkbox-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: 10px; +/* Search Input */ +.mlf-search-wrapper { + position: relative; + max-width: 500px; } -.mlf-checkbox-grid-small { - grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); - max-width: 300px; +.mlf-search-icon { + position: absolute; + left: 10px; + top: 50%; + transform: translateY(-50%); + color: #646970; + font-size: 18px; + width: 18px; + height: 18px; } -.mlf-checkbox-label { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px; - background: #f6f7f7; - border: 1px solid #dcdcde; +.mlf-search-input { + width: 100% !important; + max-width: none !important; + padding-left: 36px !important; + padding-right: 36px !important; +} + +.mlf-search-spinner { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + float: none !important; + margin: 0 !important; +} + +/* Search Results */ +.mlf-search-results { + margin-top: 12px; + border: 1px solid #c3c4c7; border-radius: 4px; + max-height: 400px; + overflow-y: auto; + background: #fff; +} + +.mlf-results-list { + padding: 0; +} + +.mlf-result-item { + padding: 16px; + border-bottom: 1px solid #e0e0e0; cursor: pointer; transition: background-color 0.15s ease; } -.mlf-checkbox-label:hover { - background: #f0f0f1; +.mlf-result-item:last-child { + border-bottom: none; } -.mlf-checkbox-label input[type="checkbox"] { - margin: 0; +.mlf-result-item:hover { + background: #f0f6fc; } -.mlf-checkbox-label input[type="checkbox"]:checked + span { +.mlf-result-info { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; +} + +.mlf-result-name { font-weight: 600; + font-size: 15px; + color: #1d2327; } -/* File Count */ -.mlf-form-row-info { - padding: 12px 16px; +.mlf-result-category { + font-size: 12px; + color: #646970; + padding: 2px 8px; + background: #f0f0f1; + border-radius: 3px; +} + +.mlf-result-badges { + display: flex; + gap: 6px; +} + +.mlf-badge { + font-size: 11px; + padding: 2px 8px; + border-radius: 3px; + font-weight: 500; +} + +.mlf-badge-variable { + background: #d4edda; + color: #155724; +} + +.mlf-result-preview { + font-size: 22px; + color: #50575e; + line-height: 1.3; + padding-top: 4px; +} + +.mlf-no-results { + padding: 24px; + text-align: center; + color: #646970; + font-style: italic; +} + +/* Selected Font */ +.mlf-selected-font { + margin-top: 16px; + padding: 16px; background: #f0f6fc; border: 1px solid #c5d9ed; border-radius: 4px; } -.mlf-file-count { +.mlf-selected-font-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid #c5d9ed; +} + +.mlf-selected-label { + color: #646970; + font-size: 13px; +} + +.mlf-selected-name { + font-weight: 600; + font-size: 16px; + color: #1d2327; + flex: 1; +} + +.mlf-change-font { + background: none; + border: none; color: #2271b1; + cursor: pointer; + font-size: 13px; + padding: 4px 8px; } -.mlf-file-count strong { - font-size: 1.1em; +.mlf-change-font:hover { + color: #135e96; + text-decoration: underline; } -/* Font Preview */ -.mlf-preview-section { - margin-top: 20px; - padding-top: 20px; - border-top: 1px solid #dcdcde; +.mlf-italic-row { + margin-bottom: 16px; } -.mlf-preview-box { +/* Italic Toggle */ +.mlf-italic-toggle { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 16px; background: #fff; border: 1px solid #dcdcde; border-radius: 4px; - padding: 24px; - min-height: 100px; - position: relative; + cursor: pointer; + font-weight: normal !important; + transition: background-color 0.15s ease; } -.mlf-preview-text { - display: flex; - flex-direction: column; - gap: 16px; +.mlf-italic-toggle:hover { + background: #f6f7f7; } -.mlf-preview-sample { - display: block; - line-height: 1.4; -} - -.mlf-preview-heading { - font-size: 28px; -} - -.mlf-preview-paragraph { - font-size: 16px; - color: #50575e; -} - -.mlf-preview-loading { - display: flex; - align-items: center; - gap: 10px; - color: #646970; - padding: 20px 0; -} - -.mlf-preview-loading .spinner { - float: none; +.mlf-italic-toggle input[type="checkbox"] { margin: 0; } -.mlf-preview-error { - color: #b32d2e; - padding: 20px 0; - text-align: center; -} - /* Submit Row */ .mlf-form-row-submit { display: flex; align-items: center; gap: 12px; + margin-top: 0; + padding-top: 0; + border-top: none; +} + +.mlf-selected-font .mlf-form-row-submit { + margin-top: 0; } .mlf-form-row-submit .spinner { @@ -189,6 +291,28 @@ color: #721c24; } +/* Info Note */ +.mlf-info-note { + display: flex; + align-items: flex-start; + gap: 8px; + margin-top: 20px; + padding: 12px 16px; + background: #f0f6fc; + border: 1px solid #c5d9ed; + border-radius: 4px; + font-size: 13px; + color: #1d2327; +} + +.mlf-info-note .dashicons { + color: #2271b1; + font-size: 18px; + width: 18px; + height: 18px; + margin-top: 1px; +} + /* Font List */ .mlf-font-list { display: flex; @@ -245,10 +369,11 @@ .mlf-no-fonts { color: #646970; font-style: italic; - padding: 20px; + padding: 24px; text-align: center; background: #f6f7f7; border-radius: 4px; + margin: 0; } /* Info Box */ @@ -264,12 +389,18 @@ .mlf-info-box .dashicons { color: #2271b1; + font-size: 20px; + width: 20px; + height: 20px; margin-top: 2px; } .mlf-info-box p { - margin: 0; - color: #1d2327; + margin: 0 0 8px 0; +} + +.mlf-info-box p:last-child { + margin-bottom: 0; } .mlf-info-box a { @@ -332,10 +463,6 @@ /* Responsive */ @media screen and (max-width: 782px) { - .mlf-checkbox-grid { - grid-template-columns: repeat(2, 1fr); - } - .mlf-font-item { flex-direction: column; align-items: flex-start; @@ -345,10 +472,12 @@ .mlf-font-actions { margin-left: 0; } -} -@media screen and (max-width: 480px) { - .mlf-checkbox-grid { - grid-template-columns: 1fr; + .mlf-result-info { + flex-wrap: wrap; + } + + .mlf-selected-font-header { + flex-wrap: wrap; } } diff --git a/native/wordpress/maple-fonts-wp/assets/admin.js b/native/wordpress/maple-fonts-wp/assets/admin.js index 7d0b014..fff43ea 100644 --- a/native/wordpress/maple-fonts-wp/assets/admin.js +++ b/native/wordpress/maple-fonts-wp/assets/admin.js @@ -9,182 +9,304 @@ var MLF = { /** - * Preview debounce timer. + * Search debounce timer. */ - previewTimer: null, + searchTimer: null, /** - * Currently loaded preview font. + * Currently selected font. */ - currentPreviewFont: null, + selectedFont: null, + + /** + * Loaded font preview stylesheets. + */ + loadedFonts: {}, /** * Initialize the admin functionality. */ init: function() { this.bindEvents(); - this.updateFileCount(); }, /** * 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)); - - // Update file count on checkbox change - $('#mlf-import-form').on('change', 'input[type="checkbox"]', this.updateFileCount.bind(this)); - - // Font name input for preview (debounced) - $('#mlf-font-name').on('input', this.handleFontNameInput.bind(this)); }, /** - * Handle font name input for preview (debounced). + * Handle search input (debounced). * * @param {Event} e Input event. */ - handleFontNameInput: function(e) { - var fontName = $(e.target).val().trim(); + handleSearchInput: function(e) { + var query = $(e.target).val().trim(); // Clear previous timer - if (this.previewTimer) { - clearTimeout(this.previewTimer); + if (this.searchTimer) { + clearTimeout(this.searchTimer); } - // Hide preview if empty - if (!fontName) { - this.hidePreview(); + // Hide results if query too short + if (query.length < 2) { + this.hideSearchResults(); return; } - // Validate font name (only allowed characters) - if (!/^[a-zA-Z0-9\s\-]+$/.test(fontName)) { - this.hidePreview(); - return; - } - - // Debounce: wait 500ms before loading preview - this.previewTimer = setTimeout(function() { - MLF.loadFontPreview(fontName); - }, 500); + // Debounce: wait 300ms before searching + this.searchTimer = setTimeout(function() { + MLF.performSearch(query); + }, 300); }, /** - * Load font preview from Google Fonts. + * Perform the search. * - * @param {string} fontName Font family name. + * @param {string} query Search query. */ - loadFontPreview: function(fontName) { - var $section = $('#mlf-preview-section'); - var $text = $('#mlf-preview-text'); - var $loading = $('#mlf-preview-loading'); - var $error = $('#mlf-preview-error'); + performSearch: function(query) { + var $spinner = $('#mlf-search-spinner'); + var $results = $('#mlf-search-results'); + var $list = $('#mlf-results-list'); - // Skip if same font already loaded - if (this.currentPreviewFont === fontName) { + // 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; } - // Show section with loading state - $section.show(); - $text.hide(); - $loading.show(); - $error.hide(); + var html = ''; + var previewText = mapleLocalFontsData.strings.previewText || 'Maple Fonts Preview'; - // Remove previous preview font link - $('#mlf-preview-font-link').remove(); + fonts.forEach(function(font) { + var fontFamily = font.family; + var category = font.category || 'sans-serif'; + var categoryLabel = MLF.getCategoryLabel(category); + var badges = []; - // Build Google Fonts URL for preview (just 400 weight for preview) - var fontFamily = fontName.replace(/\s+/g, '+'); - var previewUrl = 'https://fonts.googleapis.com/css2?family=' + encodeURIComponent(fontFamily) + ':wght@400&display=swap'; - - // Create link element - var $link = $('', { - id: 'mlf-preview-font-link', - rel: 'stylesheet', - href: previewUrl - }); - - // Handle load success - $link.on('load', function() { - MLF.currentPreviewFont = fontName; - $text.css('font-family', '"' + fontName + '", sans-serif'); - $loading.hide(); - $text.show(); - }); - - // Handle load error - $link.on('error', function() { - MLF.currentPreviewFont = null; - $loading.hide(); - $error.show(); - }); - - // Append to head - $('head').append($link); - - // Fallback timeout (5 seconds) - setTimeout(function() { - if ($loading.is(':visible')) { - // Check if font actually loaded by measuring text width - var testSpan = $('').text('test').css({ - 'font-family': '"' + fontName + '", monospace', - 'position': 'absolute', - 'visibility': 'hidden' - }).appendTo('body'); - - var testWidth = testSpan.width(); - - var fallbackSpan = $('').text('test').css({ - 'font-family': 'monospace', - 'position': 'absolute', - 'visibility': 'hidden' - }).appendTo('body'); - - var fallbackWidth = fallbackSpan.width(); - - testSpan.remove(); - fallbackSpan.remove(); - - if (testWidth !== fallbackWidth) { - // Font loaded successfully - MLF.currentPreviewFont = fontName; - $text.css('font-family', '"' + fontName + '", sans-serif'); - $loading.hide(); - $text.show(); - } else { - // Font failed to load - MLF.currentPreviewFont = null; - $loading.hide(); - $error.show(); - } + if (font.has_variable) { + badges.push('Variable'); } - }, 5000); + + 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(); }, /** - * Hide the font preview section. + * Display no results message. */ - hidePreview: function() { - this.currentPreviewFont = null; - $('#mlf-preview-section').hide(); - $('#mlf-preview-font-link').remove(); + 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(); }, /** - * Update the file count display. + * Hide search results. */ - updateFileCount: function() { - var weights = $('input[name="weights[]"]:checked').length; - var styles = $('input[name="styles[]"]:checked').length; - var count = weights * styles; + hideSearchResults: function() { + $('#mlf-search-results').hide(); + }, - $('#mlf-file-count').text(count); + /** + * 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; }, /** @@ -202,31 +324,17 @@ // Get form values var fontName = $('#mlf-font-name').val().trim(); - var weights = []; - var styles = []; - - $('input[name="weights[]"]:checked').each(function() { - weights.push($(this).val()); - }); - - $('input[name="styles[]"]:checked').each(function() { - styles.push($(this).val()); - }); + var includeItalic = $('#mlf-include-italic').is(':checked') ? '1' : '0'; // Validate if (!fontName) { - this.showMessage($message, mapleLocalFontsData.strings.enterFontName, 'error'); + this.showMessage($message, mapleLocalFontsData.strings.selectFont || 'Please select a font.', 'error'); return; } - if (weights.length === 0) { - this.showMessage($message, mapleLocalFontsData.strings.selectWeight, 'error'); - return; - } - - if (styles.length === 0) { - this.showMessage($message, mapleLocalFontsData.strings.selectStyle, 'error'); - return; + // Store original button text + if (!$button.data('original-text')) { + $button.data('original-text', $button.text()); } // Disable form @@ -243,8 +351,7 @@ action: 'mlf_download_font', nonce: mapleLocalFontsData.downloadNonce, font_name: fontName, - weights: weights, - styles: styles + include_italic: includeItalic }, success: function(response) { if (response.success) { @@ -257,20 +364,19 @@ MLF.showMessage($message, response.data.message || mapleLocalFontsData.strings.error, 'error'); } }, - error: function() { - MLF.showMessage($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') || 'Download & Install'); + $button.prop('disabled', false).text($button.data('original-text')); $spinner.removeClass('is-active'); } }); - - // Store original button text - if (!$button.data('original-text')) { - $button.data('original-text', $button.text()); - } }, /** @@ -290,6 +396,9 @@ return; } + // Store original button text + var originalText = $button.text(); + // Disable button $button.prop('disabled', true).text(mapleLocalFontsData.strings.deleting); $fontItem.addClass('mlf-loading'); @@ -311,20 +420,24 @@ // Check if any fonts remain if ($('.mlf-font-item').length === 0) { - $('#mlf-font-list').html( - '

No fonts installed yet.

' + $('#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('Delete'); + $button.prop('disabled', false).text(originalText); $fontItem.removeClass('mlf-loading'); } }, - error: function() { - alert(mapleLocalFontsData.strings.error); - $button.prop('disabled', false).text('Delete'); + 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'); } }); diff --git a/native/wordpress/maple-fonts-wp/includes/class-mlf-admin-page.php b/native/wordpress/maple-fonts-wp/includes/class-mlf-admin-page.php index a1eb373..dc33bd3 100644 --- a/native/wordpress/maple-fonts-wp/includes/class-mlf-admin-page.php +++ b/native/wordpress/maple-fonts-wp/includes/class-mlf-admin-page.php @@ -16,23 +16,6 @@ if (!defined('ABSPATH')) { */ class MLF_Admin_Page { - /** - * Available font weights. - * - * @var array - */ - private $weights = [ - 100 => 'Thin', - 200 => 'Extra Light', - 300 => 'Light', - 400 => 'Regular', - 500 => 'Medium', - 600 => 'Semi Bold', - 700 => 'Bold', - 800 => 'Extra Bold', - 900 => 'Black', - ]; - /** * Constructor. */ @@ -53,7 +36,8 @@ class MLF_Admin_Page { $installed_fonts = $registry->get_imported_fonts(); ?>
-

+

+

@@ -61,73 +45,62 @@ class MLF_Admin_Page {

+
- - -

-
- -
- -
- weights as $weight => $label) : ?> - - + +
+ + +
+

-
- -
- -