| @@ -11,8 +11,13 @@ | ||
| 11 | 11 | |
| 12 | 12 | namespace ThemeAtelier\Darkify\Includes; |
| 13 | 13 | |
| 14 | 14 | use ThemeAtelier\Darkify\Admin\Admin; |
| 15 | +use ThemeAtelier\Darkify\Admin\Assets; | |
| 16 | +use ThemeAtelier\Darkify\Admin\Menu; | |
| 17 | +use ThemeAtelier\Darkify\Admin\Rest\HelpRest; | |
| 18 | +use ThemeAtelier\Darkify\Admin\Rest\PreviewRest; | |
| 19 | +use ThemeAtelier\Darkify\Admin\Rest\SettingsRest; | |
| 15 | 20 | use ThemeAtelier\Darkify\Frontend\Frontend; |
| 16 | 21 | |
| 17 | 22 | |
| 18 | 23 | // don't call the file directly. |
| @@ -73,8 +78,16 @@ | ||
| 73 | 78 | $this->define_public_hooks(); |
| 74 | 79 | $this->init_action(); |
| 75 | 80 | add_action('plugin_loaded', array($this, 'init_plugin')); |
| 76 | 81 | |
| 82 | + // Translations load on `after_setup_theme`, not alongside the boot action | |
| 83 | + // above. `plugin_loaded` fires while plugin files are still being | |
| 84 | + // included, before the pluggable functions exist, so `determine_locale()` | |
| 85 | + // cannot yet see a per-user admin language. Priority 1 puts this ahead of | |
| 86 | + // Admin::init_components (same hook, priority 10), which is where the | |
| 87 | + // settings schema evaluates its `__()` calls. | |
| 88 | + add_action('after_setup_theme', array($this, 'load_textdomain'), 1); | |
| 89 | + | |
| 77 | 90 | add_action('elementor/controls/register', [$this, 'register_controls']); |
| 78 | 91 | add_action('elementor/widgets/register', array($this, 'register_darkify_widget')); |
| 79 | 92 | |
| 80 | 93 | add_action('init', [$this, 'create_darkify_block_init']); |
| @@ -149,9 +162,9 @@ | ||
| 149 | 162 | define('DARKIFY_ADMIN', DARKIFY_URL . '/src/Admin'); |
| 150 | 163 | } |
| 151 | 164 | |
| 152 | 165 | /** |
| 153 | - * Load the plugin after all plugins are loaded. | |
| 166 | + * Fire the plugin's own boot action. | |
| 154 | 167 | * |
| 155 | 168 | * @return void |
| 156 | 169 | */ |
| 157 | 170 | public function init_plugin() |
| @@ -156,12 +169,32 @@ | ||
| 156 | 169 | */ |
| 157 | 170 | public function init_plugin() |
| 158 | 171 | { |
| 159 | 172 | do_action('darkify_loaded'); |
| 160 | - load_plugin_textdomain('darkify', false, DARKIFY_DIR_NAME . "/languages"); | |
| 161 | 173 | } |
| 162 | 174 | |
| 163 | 175 | /** |
| 176 | + * Load the plugin's translations. | |
| 177 | + * | |
| 178 | + * @return void | |
| 179 | + */ | |
| 180 | + public function load_textdomain() | |
| 181 | + { | |
| 182 | + // `load_plugin_textdomain()` wants a path *relative to WP_PLUGIN_DIR* — | |
| 183 | + // it does `WP_PLUGIN_DIR . '/' . $path`. DARKIFY_DIR_NAME is an absolute | |
| 184 | + // path (`dirname(__FILE__)`), so the old call built a nonsense path like | |
| 185 | + // `/wp-content/plugins//Users/…/plugins/darkify/languages` and the .mo | |
| 186 | + // was never found. `wp_set_script_translations()` in Admin/Assets.php | |
| 187 | + // does take an absolute path and got it right, which is why the React | |
| 188 | + // strings translated and nothing from PHP ever did. | |
| 189 | + load_plugin_textdomain( | |
| 190 | + 'darkify', | |
| 191 | + false, | |
| 192 | + dirname(plugin_basename(DARKIFY_FILE)) . '/languages' | |
| 193 | + ); | |
| 194 | + } | |
| 195 | + | |
| 196 | + /** | |
| 164 | 197 | * Load the required dependencies for this plugin. |
| 165 | 198 | * |
| 166 | 199 | * Include the following files that make up the plugin: |
| 167 | 200 | * |
| @@ -197,8 +230,21 @@ | ||
| 197 | 230 | $plugin_admin = new Admin($this->get_plugin_name(), $this->get_version()); |
| 198 | 231 | $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_styles'); |
| 199 | 232 | $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts'); |
| 200 | 233 | $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'admin_dequeue_for_specefic_pages', 999); |
| 234 | + | |
| 235 | + // The React admin: its menu page, its bundle, and the REST endpoints it | |
| 236 | + // talks to. The controllers hook themselves onto `rest_api_init`, so they | |
| 237 | + // must be constructed on every request, not only in wp-admin. | |
| 238 | + new Menu(); | |
| 239 | + new Assets(); | |
| 240 | + new SettingsRest(); | |
| 241 | + // Backs the Get Help page's Recommended tab (ThemeAtelier plugin | |
| 242 | + // directory with one-click install/activate). | |
| 243 | + new HelpRest(); | |
| 244 | + // Constructed unconditionally (not admin-only): its `init` hook also has | |
| 245 | + // to fire on the frontend iframe request to swap in the preview values. | |
| 246 | + new PreviewRest(); | |
| 201 | 247 | } |
| 202 | 248 | |
| 203 | 249 | /** |
| 204 | 250 | * Register all of the hooks related to the public-facing functionality |
| @@ -230,9 +276,23 @@ | ||
| 230 | 276 | |
| 231 | 277 | public static function switcher_wrapper($type, $unique_id, $variant, $switch_size, $position_class = '', $floating_switch_position = '', $tooltip_classes = '', $hide_dark_mode_on_mobile_screen = '', $floating_switch_width = '', $tooltip_on_floating_switch = '') |
| 232 | 278 | { |
| 233 | 279 | |
| 234 | - return '<div style="--darkify-switch-scale: ' . esc_attr($switch_size) . ';" id="darkify_switch_' . esc_attr($unique_id) . '" onclick="darkify_switch_trigger()" class="darkify_switch darkify_switch_style switch-' . esc_attr($variant) . ' ' . ($type !== 'shortcode' ? esc_attr($position_class) . ' ' . esc_attr($floating_switch_position) . ' ' . esc_attr($tooltip_classes) . ' ' . esc_attr($hide_dark_mode_on_mobile_screen) : '') . '" ' . ($type === 'shortcode' ? 'style="--darkify-switch-scale: ' . esc_attr($switch_size) . '; position: relative;"' : '') . '><div class="theme-toggle">' . self::switcher($variant, $unique_id) . '</div>' . ($type !== 'shortcode' && $floating_switch_width ? '<span class="darkify_tooltiptext">' . esc_attr($tooltip_on_floating_switch) . '</span>' : '') . '</div>'; | |
| 280 | + // A single `style` attribute: this used to be emitted TWICE (once with | |
| 281 | + // just the scale variable, then — for `shortcode` — a second `style="..."` | |
| 282 | + // appended after `class`). HTML only honors the FIRST of two duplicate | |
| 283 | + // attributes, so that second one (the only place `position: relative` was | |
| 284 | + // set) was silently dropped by every browser. Without it, an absolutely- | |
| 285 | + // positioned inner element (e.g. a pill variant's sliding thumb) has no | |
| 286 | + // guarantee of being positioned relative to THIS wrapper — it falls back | |
| 287 | + // to whatever positioned ancestor happens to exist (or none), which is | |
| 288 | + // exactly the kind of thing a page's content wrapper (often already | |
| 289 | + // `position: relative`) can accidentally paper over while a theme's nav | |
| 290 | + // `<li>` — the Menu Switch's actual context — usually doesn't. | |
| 291 | + $style = '--darkify-switch-scale: ' . esc_attr($switch_size) . ';' . ($type === 'shortcode' ? ' position: relative;' : ''); | |
| 292 | + $classes = 'darkify_switch darkify_switch_style switch-' . esc_attr($variant) | |
| 293 | + . ($type !== 'shortcode' ? ' ' . esc_attr($position_class) . ' ' . esc_attr($floating_switch_position) . ' ' . esc_attr($tooltip_classes) . ' ' . esc_attr($hide_dark_mode_on_mobile_screen) : ''); | |
| 294 | + return '<div style="' . $style . '" id="darkify_switch_' . esc_attr($unique_id) . '" onclick="darkify_switch_trigger()" class="' . $classes . '"><div class="theme-toggle">' . self::switcher($variant, $unique_id) . '</div>' . ($type !== 'shortcode' && $floating_switch_width ? '<span class="darkify_tooltiptext">' . esc_attr($tooltip_on_floating_switch) . '</span>' : '') . '</div>'; | |
| 235 | 295 | } |
| 236 | 296 | |
| 237 | 297 | private function init_action() |
| 238 | 298 | { |
| @@ -253,8 +313,19 @@ | ||
| 253 | 313 | 'darkify-switcher-editor-script', |
| 254 | 314 | 'darkify', |
| 255 | 315 | array( |
| 256 | 316 | 'dir_url' => DARKIFY_DIR_URL, |
| 317 | + /* | |
| 318 | + * How many of the block's switch styles this plugin may select, | |
| 319 | + * counted from the start of the list in blocks/src/edit.js. | |
| 320 | + * The rest are shown as locked Pro previews. | |
| 321 | + * | |
| 322 | + * Sent from here rather than decided in the editor's CSS: the | |
| 323 | + * old lock was a `:nth-child` rule, which a mouse respected but | |
| 324 | + * the keyboard walked straight past, and which silently changed | |
| 325 | + * meaning if the style list were ever reordered. | |
| 326 | + */ | |
| 327 | + 'unlocked_styles' => 5, | |
| 257 | 328 | ) |
| 258 | 329 | ); |
| 259 | 330 | } |
| 260 | 331 | |
| @@ -284,9 +355,11 @@ | ||
| 284 | 355 | } else { |
| 285 | 356 | $switcher_size = (float) $size * 100; |
| 286 | 357 | } |
| 287 | 358 | |
| 288 | - return '<div style="display: flex; justify-content: ' . esc_attr($alignment) . '">' . do_shortcode(wp_sprintf( | |
| 359 | + // The shortcode returns an empty string when dark mode is off for the | |
| 360 | + // front end; don't leave a stray flex wrapper behind in that case. | |
| 361 | + $switch = do_shortcode(wp_sprintf( | |
| 289 | 362 | '[darkify switch="%s" icon_size="40px" light_mode_bg="%s" dark_mode_bg="%s" light_mode_color="%s" dark_mode_color="%s" border="%spx" border_light_color="%s" border_dark_color="%s" border_radius="%spx" text_light_mode_color="%s" text_dark_mode_color="%s" switch_size="%s"]', |
| 290 | 363 | $style, |
| 291 | 364 | $bg_light, |
| 292 | 365 | $bg_dark, |
| @@ -298,7 +371,13 @@ | ||
| 298 | 371 | $border_radius, |
| 299 | 372 | $light_text_color, |
| 300 | 373 | $dark_text_color, |
| 301 | 374 | $switcher_size |
| 302 | - )) . '</div>'; | |
| 375 | + )); | |
| 376 | + | |
| 377 | + if ('' === trim($switch)) { | |
| 378 | + return ''; | |
| 379 | + } | |
| 380 | + | |
| 381 | + return '<div style="display: flex; justify-content: ' . esc_attr($alignment) . '">' . $switch . '</div>'; | |
| 303 | 382 | } |
| 304 | 383 | } |