| @@ -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,24 @@ | ||
| 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 | + templately()->assets->enqueue( 'templately-tailwind', 'css/tailwind.css', [ 'templately-inter' ] ); | |
| 175 | + templately()->assets->enqueue( 'templately-deactivate-survey', 'css/deactivate-survey-style.css', [ 'templately-tailwind' ] ); | |
| 176 | + templately()->assets->enqueue( 'templately', 'js/deactivate-survey.js', [ 'regenerator-runtime' ], true ); | |
| 177 | + templately()->assets->localize( 'templately', 'templatelyDeactivateSurvey', [ | |
| 178 | + 'nonce' => wp_create_nonce( 'templately_nonce' ), | |
| 179 | + 'pluginBasename' => TEMPLATELY_PLUGIN_BASENAME, | |
| 180 | + 'reasons' => DeactivationSurvey::get_reasons_for_js(), | |
| 181 | + ] ); | |
| 182 | + return; | |
| 183 | + } | |
| 184 | + | |
| 169 | 185 | $templately = []; |
| 170 | 186 | |
| 171 | 187 | if ( 'edit.php' === $hook ) { |
| 172 | 188 | global $current_screen; |
| @@ -235,9 +251,9 @@ | ||
| 235 | 251 | |
| 236 | 252 | wp_enqueue_media(); |
| 237 | 253 | templately()->assets->enqueue( 'templately', 'js/templately.js', $script_dependencies, true ); |
| 238 | 254 | templately()->assets->enqueue( 'templately', 'css/templately.css', ['templately-dmsans', 'templately-inter'] ); |
| 239 | - $tailwind_hooks = apply_filters( 'templately_tailwind_hooks', [ 'toplevel_page_templately', 'elementor', 'gutenberg', 'templately_page_templately_settings' ] ); | |
| 255 | + $tailwind_hooks = apply_filters( 'templately_tailwind_hooks', [ 'toplevel_page_templately', 'elementor', 'gutenberg', 'templately_page_templately_settings', 'edit.php' ] ); | |
| 240 | 256 | if ( in_array( $hook, $tailwind_hooks, true ) ) { |
| 241 | 257 | templately()->assets->enqueue( 'templately-tailwind', 'css/tailwind.css', ['templately', 'templately-inter'] ); |
| 242 | 258 | } |
| 243 | 259 | |
| @@ -271,13 +287,18 @@ | ||
| 271 | 287 | 'no_items' => templately()->assets->icon( 'no-items.png' ), |
| 272 | 288 | 'loadingImage' => templately()->assets->icon( 'logos/loading-logo.gif' ), |
| 273 | 289 | 'current_url' => admin_url( 'admin.php?page=templately' ), |
| 274 | 290 | 'wp_dashboard_url' => admin_url( 'index.php' ), |
| 291 | + 'templately_library_url' => admin_url( 'edit.php?post_type=templately_library' ), | |
| 275 | 292 | 'is_signed' => Login::is_signed(), |
| 276 | 293 | 'is_globally_signed' => Login::is_globally_signed(), |
| 277 | 294 | 'signed_as_global' => Login::signed_as_global(), |
| 278 | 295 | 'current_screen' => $_current_screen, |
| 279 | 296 | 'can_fsi' => current_user_can('install_plugins') && current_user_can('install_themes'), |
| 297 | + // Account-level administration: the Subscription screen. Mirrors the | |
| 298 | + // subscription branch of `Checkout::checkout()`, so the sidebar can hide | |
| 299 | + // it for non-admins rather than route them to a 403. | |
| 300 | + 'can_manage_account' => current_user_can( 'manage_options' ), | |
| 280 | 301 | 'post_type' => get_post_type(), |
| 281 | 302 | 'has_elementor' => rest_sanitize_boolean( is_plugin_active( 'elementor/elementor.php' ) ), |
| 282 | 303 | 'has_elementor_pro' => rest_sanitize_boolean( is_plugin_active( 'elementor-pro/elementor-pro.php' ) ), |
| 283 | 304 | 'theme' => $_current_screen == 'templately' ? 'light' : $platform->ui_theme(), |
| @@ -288,8 +309,15 @@ | ||
| 288 | 309 | 'allowed_mime_types' => get_allowed_mime_types(), |
| 289 | 310 | 'is_free_user' => $global_user === false || isset( $global_user['plan'] ) && $global_user['plan'] == 'free', |
| 290 | 311 | 'is_disconnected' => ! empty( $global_user['is_disconnected'] ), |
| 291 | 312 | 'tour_status' => Options::get_instance()->get( 'tour_status', [] ), |
| 313 | + // Which Templately site the plugin talks to. `templatelyDeveloper.dev_api` | |
| 314 | + // carries the same flag, but only when developer mode is on and only from | |
| 315 | + // its own script handle — so anything rendered before that handle runs (the | |
| 316 | + // footer CTA, for one) linked to the live site while the plugin was pointed | |
| 317 | + // at dev. Kept here, on the main localized object, `webURL()` is right from | |
| 318 | + // the first paint. See `react-src/utils/helper.js#webURL`. | |
| 319 | + 'dev_api' => Helper::is_dev_api(), | |
| 292 | 320 | ], $templately ); |
| 293 | 321 | |
| 294 | 322 | // Apply filter to allow network admin modifications |
| 295 | 323 | $templately = apply_filters( 'templately_admin_localized_data', $templately ); |
| @@ -367,9 +395,9 @@ | ||
| 367 | 395 | 'class' => 'dismiss-btn' |
| 368 | 396 | ] |
| 369 | 397 | ], |
| 370 | 398 | 'support' => [ |
| 371 | - 'link' => 'https://templately.com/?support=open', | |
| 399 | + 'link' => Helper::web_url( '', [ 'support' => 'open' ] ), | |
| 372 | 400 | 'attributes' => [ |
| 373 | 401 | 'target' => '_blank' |
| 374 | 402 | ], |
| 375 | 403 | 'label' => __( 'I need help', 'templately' ), |
| @@ -431,9 +459,9 @@ | ||
| 431 | 459 | 'thumbnail' => templately()->assets->icon( 'logos/logo-full.svg' ), |
| 432 | 460 | 'html' => $_notice_text_spring_savings_2026, |
| 433 | 461 | 'links' => [ |
| 434 | 462 | 'later' => [ |
| 435 | - 'link' => 'https://templately.com/#pricing', | |
| 463 | + 'link' => Helper::web_url() . '#pricing', | |
| 436 | 464 | 'target' => '_blank', |
| 437 | 465 | 'label' => __( 'Upgrade To Pro Now', 'templately' ), |
| 438 | 466 | 'attributes' => [ |
| 439 | 467 | 'class' => 'button button-primary', |
| @@ -471,9 +499,9 @@ | ||
| 471 | 499 | $holiday_text .= "<div class='wpnotice-button-wrapper'>"; |
| 472 | 500 | $holiday_text .= sprintf( |
| 473 | 501 | '<a class="button button-primary" target="_blank" href="%2$s">%1$s</a>', |
| 474 | 502 | $crown . __('GET PRO Lifetime Access', 'templately'), |
| 475 | - 'https://templately.com/#pricing' | |
| 503 | + Helper::web_url() . '#pricing' | |
| 476 | 504 | ); |
| 477 | 505 | $holiday_text .= sprintf( |
| 478 | 506 | '<button class="button button-link dismiss-btn" data-dismiss="true">%1$s</button>', |
| 479 | 507 | __('No, I\'ll Pay Full Price Later', 'templately'), |
| @@ -526,9 +554,44 @@ | ||
| 526 | 554 | // ], '58.7' ); |
| 527 | 555 | } |
| 528 | 556 | |
| 529 | 557 | public function display() { |
| 530 | - Helper::views( 'template-library' ); | |
| 558 | + Helper::views( 'template-library', [ | |
| 559 | + 'show_grid_skeleton' => $this->is_template_library_path(), | |
| 560 | + ] ); | |
| 561 | + } | |
| 562 | + | |
| 563 | + /** | |
| 564 | + * Whether the current `?path=` targets the template-library grid | |
| 565 | + * (`TemplatesTypeLayout`, e.g. `elementor/packs`) — the only route the | |
| 566 | + * pre-mount skeleton's card grid actually matches. Every other route | |
| 567 | + * (`clouds/*`, `downloads`, `favourites`, `subscription`, | |
| 568 | + * `purchased-items`, `ai-sites`, `settings/*`, item-detail pages, and | |
| 569 | + * Saved Templates) gets a generic shell instead, so a hard reload doesn't | |
| 570 | + * flash the wrong page shape before React mounts. See | |
| 571 | + * views/template-library.php. | |
| 572 | + * | |
| 573 | + * @return bool | |
| 574 | + */ | |
| 575 | + private function is_template_library_path() { | |
| 576 | + $path = isset( $_GET['path'] ) ? sanitize_text_field( wp_unslash( $_GET['path'] ) ) : ''; | |
| 577 | + | |
| 578 | + if ( $path === '' ) { | |
| 579 | + return true; | |
| 580 | + } | |
| 581 | + | |
| 582 | + $segments = array_values( array_filter( explode( '/', trim( $path, '/' ) ) ) ); | |
| 583 | + | |
| 584 | + $non_library_prefixes = [ 'clouds', 'downloads', 'favourites', 'subscription', 'purchased-items', 'ai-sites', 'settings' ]; | |
| 585 | + | |
| 586 | + if ( in_array( $segments[0] ?? '', $non_library_prefixes, true ) ) { | |
| 587 | + return false; | |
| 588 | + } | |
| 589 | + | |
| 590 | + // `/:platform/:type` (the grid) vs `/:platform/:type/:slug` (item | |
| 591 | + // detail, its own skeleton shape) or `/:platform/templates` (Saved | |
| 592 | + // Templates, a table) — only the 2-segment form matches the grid. | |
| 593 | + return count( $segments ) === 2 && $segments[1] !== 'templates'; | |
| 531 | 594 | } |
| 532 | 595 | |
| 533 | 596 | /** |
| 534 | 597 | * If Elementor doesn't exists. |