| @@ -103,9 +103,9 @@ | ||
| 103 | 103 | */ |
| 104 | 104 | public function scripts( $hook = '' ) { |
| 105 | 105 | $allowed_hooks = apply_filters( |
| 106 | 106 | 'templately_admin_enqueue_allowed_hooks', |
| 107 | - [ 'index.php', 'edit.php', 'toplevel_page_templately', 'elementor', 'gutenberg', 'templately_page_templately_settings' ], | |
| 107 | + [ 'index.php', 'edit.php', 'plugins.php', 'toplevel_page_templately', 'elementor', 'gutenberg', 'templately_page_templately_settings' ], | |
| 108 | 108 | $hook |
| 109 | 109 | ); |
| 110 | 110 | |
| 111 | 111 | if ( !empty($hook) && ! in_array( $hook, $allowed_hooks, true ) ) { |
| @@ -165,8 +165,34 @@ | ||
| 165 | 165 | } |
| 166 | 166 | return; |
| 167 | 167 | } |
| 168 | 168 | |
| 169 | + if ( 'plugins.php' === $hook ) { | |
| 170 | + templately()->assets->enqueue( | |
| 171 | + 'templately-inter', | |
| 172 | + set_url_scheme( '//fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap' ) | |
| 173 | + ); | |
| 174 | + /** | |
| 175 | + * plugins.php is not our screen, so the survey gets the scoped Tailwind | |
| 176 | + * build rather than css/tailwind.css. The unscoped bundle is compiled | |
| 177 | + * with `important: true` and no layer, so its utilities outranked | |
| 178 | + * wp-admin's own rules — `.inline` beat `.notice.inline` and `.hidden`, | |
| 179 | + * which collapsed the update notices in every plugin row into coloured | |
| 180 | + * slivers and revealed the hidden auto-update error placeholders. | |
| 181 | + * css/tailwind-survey.css carries the same rules prefixed with | |
| 182 | + * `.templately-scope`, the container the modal mounts into. | |
| 183 | + */ | |
| 184 | + templately()->assets->enqueue( 'templately-tailwind-survey', 'css/tailwind-survey.css', [ 'templately-inter' ] ); | |
| 185 | + templately()->assets->enqueue( 'templately-deactivate-survey', 'css/deactivate-survey-style.css', [ 'templately-tailwind-survey' ] ); | |
| 186 | + templately()->assets->enqueue( 'templately', 'js/deactivate-survey.js', [ 'regenerator-runtime' ], true ); | |
| 187 | + templately()->assets->localize( 'templately', 'templatelyDeactivateSurvey', [ | |
| 188 | + 'nonce' => wp_create_nonce( 'templately_nonce' ), | |
| 189 | + 'pluginBasename' => TEMPLATELY_PLUGIN_BASENAME, | |
| 190 | + 'reasons' => DeactivationSurvey::get_reasons_for_js(), | |
| 191 | + ] ); | |
| 192 | + return; | |
| 193 | + } | |
| 194 | + | |
| 169 | 195 | $templately = []; |
| 170 | 196 | |
| 171 | 197 | if ( 'edit.php' === $hook ) { |
| 172 | 198 | global $current_screen; |
| @@ -277,8 +303,12 @@ | ||
| 277 | 303 | 'is_globally_signed' => Login::is_globally_signed(), |
| 278 | 304 | 'signed_as_global' => Login::signed_as_global(), |
| 279 | 305 | 'current_screen' => $_current_screen, |
| 280 | 306 | 'can_fsi' => current_user_can('install_plugins') && current_user_can('install_themes'), |
| 307 | + // Account-level administration: the Subscription screen. Mirrors the | |
| 308 | + // subscription branch of `Checkout::checkout()`, so the sidebar can hide | |
| 309 | + // it for non-admins rather than route them to a 403. | |
| 310 | + 'can_manage_account' => current_user_can( 'manage_options' ), | |
| 281 | 311 | 'post_type' => get_post_type(), |
| 282 | 312 | 'has_elementor' => rest_sanitize_boolean( is_plugin_active( 'elementor/elementor.php' ) ), |
| 283 | 313 | 'has_elementor_pro' => rest_sanitize_boolean( is_plugin_active( 'elementor-pro/elementor-pro.php' ) ), |
| 284 | 314 | 'theme' => $_current_screen == 'templately' ? 'light' : $platform->ui_theme(), |
| @@ -289,8 +319,15 @@ | ||
| 289 | 319 | 'allowed_mime_types' => get_allowed_mime_types(), |
| 290 | 320 | 'is_free_user' => $global_user === false || isset( $global_user['plan'] ) && $global_user['plan'] == 'free', |
| 291 | 321 | 'is_disconnected' => ! empty( $global_user['is_disconnected'] ), |
| 292 | 322 | 'tour_status' => Options::get_instance()->get( 'tour_status', [] ), |
| 323 | + // Which Templately site the plugin talks to. `templatelyDeveloper.dev_api` | |
| 324 | + // carries the same flag, but only when developer mode is on and only from | |
| 325 | + // its own script handle — so anything rendered before that handle runs (the | |
| 326 | + // footer CTA, for one) linked to the live site while the plugin was pointed | |
| 327 | + // at dev. Kept here, on the main localized object, `webURL()` is right from | |
| 328 | + // the first paint. See `react-src/utils/helper.js#webURL`. | |
| 329 | + 'dev_api' => Helper::is_dev_api(), | |
| 293 | 330 | ], $templately ); |
| 294 | 331 | |
| 295 | 332 | // Apply filter to allow network admin modifications |
| 296 | 333 | $templately = apply_filters( 'templately_admin_localized_data', $templately ); |
| @@ -368,9 +405,9 @@ | ||
| 368 | 405 | 'class' => 'dismiss-btn' |
| 369 | 406 | ] |
| 370 | 407 | ], |
| 371 | 408 | 'support' => [ |
| 372 | - 'link' => 'https://templately.com/?support=open', | |
| 409 | + 'link' => Helper::web_url( '', [ 'support' => 'open' ] ), | |
| 373 | 410 | 'attributes' => [ |
| 374 | 411 | 'target' => '_blank' |
| 375 | 412 | ], |
| 376 | 413 | 'label' => __( 'I need help', 'templately' ), |
| @@ -432,9 +469,9 @@ | ||
| 432 | 469 | 'thumbnail' => templately()->assets->icon( 'logos/logo-full.svg' ), |
| 433 | 470 | 'html' => $_notice_text_spring_savings_2026, |
| 434 | 471 | 'links' => [ |
| 435 | 472 | 'later' => [ |
| 436 | - 'link' => 'https://templately.com/#pricing', | |
| 473 | + 'link' => Helper::web_url() . '#pricing', | |
| 437 | 474 | 'target' => '_blank', |
| 438 | 475 | 'label' => __( 'Upgrade To Pro Now', 'templately' ), |
| 439 | 476 | 'attributes' => [ |
| 440 | 477 | 'class' => 'button button-primary', |
| @@ -472,9 +509,9 @@ | ||
| 472 | 509 | $holiday_text .= "<div class='wpnotice-button-wrapper'>"; |
| 473 | 510 | $holiday_text .= sprintf( |
| 474 | 511 | '<a class="button button-primary" target="_blank" href="%2$s">%1$s</a>', |
| 475 | 512 | $crown . __('GET PRO Lifetime Access', 'templately'), |
| 476 | - 'https://templately.com/#pricing' | |
| 513 | + Helper::web_url() . '#pricing' | |
| 477 | 514 | ); |
| 478 | 515 | $holiday_text .= sprintf( |
| 479 | 516 | '<button class="button button-link dismiss-btn" data-dismiss="true">%1$s</button>', |
| 480 | 517 | __('No, I\'ll Pay Full Price Later', 'templately'), |
| @@ -527,9 +564,44 @@ | ||
| 527 | 564 | // ], '58.7' ); |
| 528 | 565 | } |
| 529 | 566 | |
| 530 | 567 | public function display() { |
| 531 | - Helper::views( 'template-library' ); | |
| 568 | + Helper::views( 'template-library', [ | |
| 569 | + 'show_grid_skeleton' => $this->is_template_library_path(), | |
| 570 | + ] ); | |
| 571 | + } | |
| 572 | + | |
| 573 | + /** | |
| 574 | + * Whether the current `?path=` targets the template-library grid | |
| 575 | + * (`TemplatesTypeLayout`, e.g. `elementor/packs`) — the only route the | |
| 576 | + * pre-mount skeleton's card grid actually matches. Every other route | |
| 577 | + * (`clouds/*`, `downloads`, `favourites`, `subscription`, | |
| 578 | + * `purchased-items`, `ai-sites`, `settings/*`, item-detail pages, and | |
| 579 | + * Saved Templates) gets a generic shell instead, so a hard reload doesn't | |
| 580 | + * flash the wrong page shape before React mounts. See | |
| 581 | + * views/template-library.php. | |
| 582 | + * | |
| 583 | + * @return bool | |
| 584 | + */ | |
| 585 | + private function is_template_library_path() { | |
| 586 | + $path = isset( $_GET['path'] ) ? sanitize_text_field( wp_unslash( $_GET['path'] ) ) : ''; | |
| 587 | + | |
| 588 | + if ( $path === '' ) { | |
| 589 | + return true; | |
| 590 | + } | |
| 591 | + | |
| 592 | + $segments = array_values( array_filter( explode( '/', trim( $path, '/' ) ) ) ); | |
| 593 | + | |
| 594 | + $non_library_prefixes = [ 'clouds', 'downloads', 'favourites', 'subscription', 'purchased-items', 'ai-sites', 'settings' ]; | |
| 595 | + | |
| 596 | + if ( in_array( $segments[0] ?? '', $non_library_prefixes, true ) ) { | |
| 597 | + return false; | |
| 598 | + } | |
| 599 | + | |
| 600 | + // `/:platform/:type` (the grid) vs `/:platform/:type/:slug` (item | |
| 601 | + // detail, its own skeleton shape) or `/:platform/templates` (Saved | |
| 602 | + // Templates, a table) — only the 2-segment form matches the grid. | |
| 603 | + return count( $segments ) === 2 && $segments[1] !== 'templates'; | |
| 532 | 604 | } |
| 533 | 605 | |
| 534 | 606 | /** |
| 535 | 607 | * If Elementor doesn't exists. |