| @@ -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 | |
| @@ -197,10 +284,15 @@ | ||
| 197 | 284 | )); |
| 198 | 285 | wp_enqueue_script('easy-invoice-bulk-send-email-teaser'); |
| 199 | 286 | } |
| 200 | 287 | |
| 201 | - // jsPDF for PDF generation - available on all Easy Invoice pages | |
| 202 | - wp_register_script('jspdf', 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js', array(), '2.5.1', true); | |
| 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'); | |
| 203 | 295 | wp_enqueue_script('jspdf'); |
| 204 | 296 | |
| 205 | 297 | |
| 206 | 298 | // Conditionally load invoice-specific scripts only on invoice pages |