| @@ -48,15 +48,58 @@ | ||
| 48 | 48 | * Enqueue stylesheets |
| 49 | 49 | */ |
| 50 | 50 | private function enqueueStyles() { |
| 51 | 51 | |
| 52 | + // Tailwind: the purged ~47 KB build is the default; the full 2.8 MB | |
| 53 | + // dump stays on disk as the fallback. | |
| 54 | + // | |
| 55 | + // The purged file is the shipped v2 bundle with unused rules removed -- | |
| 56 | + // NOT a v3 rebuild, which would rename utilities and change the palette. | |
| 57 | + // Every rule it keeps is byte-identical to the full file, so anything | |
| 58 | + // still on a page renders exactly as before. It was verified by | |
| 59 | + // pixel-diffing 41 admin screens (every core page, every addon page, | |
| 60 | + // three breakpoints, the drawer, a modal and toasts) against the full | |
| 61 | + // file: zero differing pixels. See tools/tailwind-build/README.md. | |
| 62 | + // | |
| 63 | + // A site that adds markup using utilities the plugin never uses -- | |
| 64 | + // a custom template, a child theme -- can go back to the full file: | |
| 65 | + // update_option('easy_invoice_tailwind_purged', '0'); | |
| 66 | + // or | |
| 67 | + // add_filter('easy_invoice_use_purged_tailwind', '__return_false'); | |
| 68 | + // | |
| 69 | + // The option was previously opt-IN ('1' enabled the purged file). It is | |
| 70 | + // now opt-OUT: only an explicit '0' disables it. A site that had set | |
| 71 | + // '1' keeps working; a site that never set it gets the win. | |
| 72 | + // | |
| 73 | + // The file_exists check below means a deployment that lost the purged | |
| 74 | + // file falls back to the full one rather than 404-ing the stylesheet. | |
| 75 | + $purged_option = get_option('easy_invoice_tailwind_purged', '1'); | |
| 76 | + $use_purged = apply_filters( | |
| 77 | + 'easy_invoice_use_purged_tailwind', | |
| 78 | + '0' !== (string) $purged_option && false !== $purged_option | |
| 79 | + ); | |
| 80 | + $purged_rel = 'assets/lib/tailwind/tailwind.purged.min.css'; | |
| 81 | + $shipped_rel = 'assets/lib/tailwind/tailwind.min.css'; | |
| 82 | + $tailwind_rel = ($use_purged && file_exists(EASY_INVOICE_PLUGIN_DIR . $purged_rel)) | |
| 83 | + ? $purged_rel | |
| 84 | + : $shipped_rel; | |
| 85 | + | |
| 52 | 86 | wp_enqueue_style( |
| 53 | 87 | 'easy-invoice-tailwind', |
| 54 | - EASY_INVOICE_PLUGIN_URL . 'assets/lib/tailwind/tailwind.min.css', | |
| 88 | + EASY_INVOICE_PLUGIN_URL . $tailwind_rel, | |
| 55 | 89 | array(), |
| 56 | 90 | EASY_INVOICE_VERSION |
| 57 | 91 | ); |
| 58 | 92 | |
| 93 | + // Colour names the views use that Tailwind v2 never had (amber, emerald, | |
| 94 | + // teal …) — without these every amber notice and emerald badge was unstyled. | |
| 95 | + wp_enqueue_style( | |
| 96 | + 'easy-invoice-palette-ext', | |
| 97 | + EASY_INVOICE_PLUGIN_URL . 'assets/css/palette-ext.css', | |
| 98 | + array( 'easy-invoice-tailwind' ), | |
| 99 | + EASY_INVOICE_VERSION | |
| 100 | + ); | |
| 101 | + | |
| 59 | 102 | $page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : ''; |
| 60 | 103 | |
| 61 | 104 | // Main plugin layout + header strip alignment (always on Easy Invoice admin screens). |
| 62 | 105 | wp_enqueue_style( |
| @@ -76,18 +119,47 @@ | ||
| 76 | 119 | EASY_INVOICE_VERSION |
| 77 | 120 | ); |
| 78 | 121 | } |
| 79 | 122 | |
| 80 | - wp_enqueue_style( | |
| 81 | - 'font-awesome', | |
| 82 | - EASY_INVOICE_PLUGIN_URL . 'assets/lib/font-awesome/css/all.min.css', | |
| 83 | - array(), | |
| 84 | - EASY_INVOICE_VERSION | |
| 85 | - ); | |
| 123 | + // Font Awesome (~87 KB) is used by the heavyweight templates | |
| 124 | + // (clients, settings, reports, invoices, quotes, payments) but | |
| 125 | + // NOT by the lightweight admin pages — Dashboard, Addons grid, | |
| 126 | + // License, Free-vs-Pro, Join Community, and every addon-settings | |
| 127 | + // sub-page (those use dashicons). Skip enqueueing FA on the | |
| 128 | + // deny-listed pages to save the bandwidth and the parse cost. | |
| 129 | + // Filterable so site-specific custom templates can opt back in. | |
| 130 | + $fa_deny = (array) apply_filters('easy_invoice_font_awesome_skip_pages', [ | |
| 131 | + 'easy-invoice-dashboard', | |
| 132 | + 'easy-invoice-addons', | |
| 133 | + 'easy-invoice-license', | |
| 134 | + 'easy-invoice-free-vs-pro', | |
| 135 | + 'easy-invoice-join-community', | |
| 136 | + ]); | |
| 137 | + $is_addon_subpage = ($page !== '' && strpos($page, 'easy-invoice-addon-') === 0); | |
| 138 | + if (!in_array($page, $fa_deny, true) && !$is_addon_subpage) { | |
| 139 | + wp_enqueue_style( | |
| 140 | + 'font-awesome', | |
| 141 | + EASY_INVOICE_PLUGIN_URL . 'assets/lib/font-awesome/css/all.min.css', | |
| 142 | + array(), | |
| 143 | + EASY_INVOICE_VERSION | |
| 144 | + ); | |
| 145 | + } | |
| 86 | 146 | |
| 87 | 147 | |
| 88 | - // Add RTL support | |
| 89 | - wp_style_add_data('easy-invoice-admin', 'rtl', 'replace'); | |
| 148 | + // RTL support. | |
| 149 | + // | |
| 150 | + // Strategy: 'append' (not 'replace') because Tailwind utility | |
| 151 | + // classes generated into easy-invoice.css remain valid LTR — only | |
| 152 | + // the small set of custom directional rules (sidebar borders, | |
| 153 | + // page-header negative margins, drawer offsets) need mirroring. | |
| 154 | + // WordPress loads assets/css/easy-invoice-rtl.css AFTER | |
| 155 | + // easy-invoice.css when is_rtl() is true; that file holds only the | |
| 156 | + // directional overrides. | |
| 157 | + // | |
| 158 | + // 'easy-invoice-main' is the handle of the file we want to extend | |
| 159 | + // (line 87 above). The previous handle 'easy-invoice-admin' did | |
| 160 | + // not exist and so the call was a no-op. | |
| 161 | + wp_style_add_data('easy-invoice-main', 'rtl', 'append'); | |
| 90 | 162 | } |
| 91 | 163 | |
| 92 | 164 | /** |
| 93 | 165 | * Enqueue scripts |
| @@ -113,12 +185,27 @@ | ||
| 113 | 185 | wp_enqueue_script('wp-hooks'); |
| 114 | 186 | |
| 115 | 187 | // Settings page script |
| 116 | 188 | if (isset($_GET['page']) && $_GET['page'] === 'easy-invoice-settings') { |
| 189 | + // settings.js calls .sortable() and .disableSelection() on the | |
| 190 | + // payment-gateway list, so jQuery UI Sortable must be present and | |
| 191 | + // printed first. SettingsController::enqueueAdminScripts() also | |
| 192 | + // registers this same handle; WordPress keeps whichever call runs | |
| 193 | + // first and silently drops the other's dependency array, and this | |
| 194 | + // one runs first (AdminAssets is wired up during EasyInvoice::init, | |
| 195 | + // before SettingsController::init). Declaring the jQuery UI handles | |
| 196 | + // here lets wp_scripts resolve the order rather than leaving it to | |
| 197 | + // the sequence the two enqueue callbacks happen to fire in. | |
| 198 | + // | |
| 199 | + // Only core handles are listed. select2 / easy-invoice-toast are | |
| 200 | + // also used by settings.js but are registered by SettingsController | |
| 201 | + // later in the request; naming them here would mean settings.js is | |
| 202 | + // dropped entirely if that callback ever stops running, so they are | |
| 203 | + // deliberately left out. | |
| 117 | 204 | wp_enqueue_script( |
| 118 | 205 | 'easy-invoice-settings', |
| 119 | 206 | EASY_INVOICE_PLUGIN_URL . 'assets/js/settings.js', |
| 120 | - array('jquery', 'wp-util', 'wp-api-fetch', 'wp-i18n'), | |
| 207 | + array('jquery', 'jquery-ui-core', 'jquery-ui-sortable', 'wp-util', 'wp-api-fetch', 'wp-i18n'), | |
| 121 | 208 | EASY_INVOICE_VERSION, |
| 122 | 209 | true |
| 123 | 210 | ); |
| 124 | 211 | |
| @@ -166,10 +253,46 @@ | ||
| 166 | 253 | // Toast notification system - global across the plugin |
| 167 | 254 | wp_register_script('easy-invoice-toast', EASY_INVOICE_PLUGIN_URL . 'assets/js/easy-invoice-toast.js', array('jquery', 'wp-api-fetch', 'wp-i18n'), EASY_INVOICE_VERSION, true); |
| 168 | 255 | wp_enqueue_script('easy-invoice-toast'); |
| 169 | 256 | |
| 170 | - // jsPDF for PDF generation - available on all Easy Invoice pages | |
| 171 | - wp_register_script('jspdf', 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js', array(), '2.5.1', true); | |
| 257 | + // Bulk "Send Email" teaser — only on the invoice / quote listings. | |
| 258 | + // Always injects the option (even without Pro) so users discover the | |
| 259 | + // feature; when Pro is inactive, clicking Apply opens the upgrade | |
| 260 | + // dialog instead of doing the work. | |
| 261 | + $current_page = isset($_GET['page']) ? sanitize_key($_GET['page']) : ''; | |
| 262 | + if (in_array($current_page, ['easy-invoice-all', 'easy-quote-all'], true)) { | |
| 263 | + wp_register_script( | |
| 264 | + 'easy-invoice-bulk-send-email-teaser', | |
| 265 | + EASY_INVOICE_PLUGIN_URL . 'assets/js/bulk-send-email-teaser.js', | |
| 266 | + array('jquery', 'easy-invoice-confirmation-modal', 'easy-invoice-toast'), | |
| 267 | + EASY_INVOICE_VERSION, | |
| 268 | + true | |
| 269 | + ); | |
| 270 | + wp_localize_script('easy-invoice-bulk-send-email-teaser', 'easyInvoiceBulkSendTeaser', array( | |
| 271 | + 'hasPro' => function_exists('easy_invoice_has_pro') && easy_invoice_has_pro(), | |
| 272 | + 'upgradeUrl' => 'https://matrixaddons.com/plugins/easy-invoice/#pricing', | |
| 273 | + 'i18n' => array( | |
| 274 | + // Send Email | |
| 275 | + 'send_email_label' => __('Send Email (Pro)', 'easy-invoice'), | |
| 276 | + 'send_email_feature_name' => __('Bulk Send Email', 'easy-invoice'), | |
| 277 | + 'send_email_feature_description'=> __('Select multiple invoices or quotes and send the configured "Available" email to every selected document\'s client in a single click. Includes a per-row success / failure report so you can spot deliverability problems immediately.', 'easy-invoice'), | |
| 278 | + // Export | |
| 279 | + 'export_feature_name' => __('Bulk Export Selected', 'easy-invoice'), | |
| 280 | + 'export_feature_description' => __('Export your selected invoices or quotes to a clean, accounting-ready CSV — perfect for QuickBooks, Xero, audit trails, or migrating to a new system.', 'easy-invoice'), | |
| 281 | + // Generic | |
| 282 | + 'upgrade_required' => __('This action requires Easy Invoice Pro.', 'easy-invoice'), | |
| 283 | + ), | |
| 284 | + )); | |
| 285 | + wp_enqueue_script('easy-invoice-bulk-send-email-teaser'); | |
| 286 | + } | |
| 287 | + | |
| 288 | + // jsPDF + html2canvas for PDF generation - available on all Easy Invoice pages. | |
| 289 | + // Both are bundled in assets/js/vendors/ rather than loaded from a CDN: | |
| 290 | + // WordPress.org requires every asset to ship inside the plugin, and a remote | |
| 291 | + // CDN is a hard runtime dependency for a core feature. | |
| 292 | + wp_register_script('html2canvas', EASY_INVOICE_PLUGIN_URL . 'assets/js/vendors/html2canvas.min.js', array(), '1.4.1', true); | |
| 293 | + wp_register_script('jspdf', EASY_INVOICE_PLUGIN_URL . 'assets/js/vendors/jspdf.umd.min.js', array(), '2.5.1', true); | |
| 294 | + wp_enqueue_script('html2canvas'); | |
| 172 | 295 | wp_enqueue_script('jspdf'); |
| 173 | 296 | |
| 174 | 297 | |
| 175 | 298 | // Conditionally load invoice-specific scripts only on invoice pages |