diff --git a/.github/workflows/e2e-js.yml b/.github/workflows/e2e-js.yml index f8fa71826..c52dcd97f 100644 --- a/.github/workflows/e2e-js.yml +++ b/.github/workflows/e2e-js.yml @@ -10,11 +10,11 @@ jobs: e2e: runs-on: ubuntu-latest if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name - + steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - + - name: Get Composer Cache Directory id: composer-cache run: | @@ -40,11 +40,11 @@ jobs: key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node-cache- - + - name: npm ci run: | npm ci - + - name: Make dev build run: | npm run build-dev @@ -60,11 +60,11 @@ jobs: - name: Install Playwright run: | npm install -g playwright-cli - npx playwright install + npx playwright install - - name: Playwright Blocks + - name: Playwright Blocks run: | - npm run test:e2e:playwright + npm run test:e2e:playwright # run the node.js puppeteer script (which takes the screenshots and controls chrome) - name: Performance check @@ -80,9 +80,9 @@ jobs: uses: actions/upload-artifact@v2 with: name: artifact - path: ./artifacts/tests/ + path: ./artifacts retention-days: 1 - + - name: Print the results run: | echo "::set-output name=TYPING_AVG::$(jq '.summary.type.average' ./artifacts/performance.spec.performance-results.json)" @@ -91,7 +91,7 @@ jobs: echo "::set-output name=TYPING_QR60::$(jq '.summary.type.quantileRank60' ./artifacts/performance.spec.performance-results.json)" echo "::set-output name=TYPING_ABOVE_60::$(jq -c '.summary.type.above60' ./artifacts/performance.spec.performance-results.json)" echo "::set-output name=TYPING_QR80::$(jq '.summary.type.quantileRank80' ./artifacts/performance.spec.performance-results.json)" - id: summary + id: summary - name: Comment uses: NejcZdovc/comment-pr@v1 @@ -107,5 +107,5 @@ jobs: TYPING_QR80: ${{ steps.summary.outputs.TYPING_QR80 }} TYPING_ABOVE_60: ${{ steps.summary.outputs.TYPING_ABOVE_60 }} - - + + diff --git a/assets/images/black-friday-banner.png b/assets/images/black-friday-banner.png index e2998d6fd..47b17aec0 100644 Binary files a/assets/images/black-friday-banner.png and b/assets/images/black-friday-banner.png differ diff --git a/composer.json b/composer.json index 18ff771de..ff8e2286a 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "yoast/phpunit-polyfills": "^2.0", "phpstan/phpstan": "^1.10", "szepeviktor/phpstan-wordpress": "^1.3", - "php-stubs/woocommerce-stubs": "^7.7", + "php-stubs/woocommerce-stubs": "^8.0", "php-stubs/acf-pro-stubs": "^6.0", "spaze/phpstan-stripe": "^2.4" }, diff --git a/composer.lock b/composer.lock index 9aea7916e..aa7b44ea2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "006c7c213e5951781d71f00b7b99b212", + "content-hash": "37c7bba284025871d7134809613f51a9", "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.3", + "version": "3.3.6", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "4f7e367b6a33b41ced763e261e7a3dc3342f6330" + "reference": "bf61570bb8d700098fbcf44e2f4258fd3f2ec5c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/4f7e367b6a33b41ced763e261e7a3dc3342f6330", - "reference": "4f7e367b6a33b41ced763e261e7a3dc3342f6330", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/bf61570bb8d700098fbcf44e2f4258fd3f2ec5c2", + "reference": "bf61570bb8d700098fbcf44e2f4258fd3f2ec5c2", "shasum": "" }, "require-dev": { @@ -42,9 +42,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.3" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.6" }, - "time": "2023-08-22T07:22:05+00:00" + "time": "2023-10-05T07:53:06+00:00" }, { "name": "masterminds/html5", @@ -168,16 +168,16 @@ }, { "name": "stripe/stripe-php", - "version": "v12.1.0", + "version": "v12.5.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "8ef18513811e3ad0cac50f699deac4032409ae07" + "reference": "a4249b4a90437844f6c35e8701f8c68acd206f56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/8ef18513811e3ad0cac50f699deac4032409ae07", - "reference": "8ef18513811e3ad0cac50f699deac4032409ae07", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/a4249b4a90437844f6c35e8701f8c68acd206f56", + "reference": "a4249b4a90437844f6c35e8701f8c68acd206f56", "shasum": "" }, "require": { @@ -190,8 +190,7 @@ "friendsofphp/php-cs-fixer": "3.5.0", "php-coveralls/php-coveralls": "^2.5", "phpstan/phpstan": "^1.2", - "phpunit/phpunit": "^5.7 || ^9.0", - "squizlabs/php_codesniffer": "^3.3" + "phpunit/phpunit": "^5.7 || ^9.0" }, "type": "library", "extra": { @@ -223,9 +222,9 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v12.1.0" + "source": "https://github.com/stripe/stripe-php/tree/v12.5.0" }, - "time": "2023-08-31T20:23:14+00:00" + "time": "2023-09-28T23:06:27+00:00" }, { "name": "tubalmartin/cssmin", @@ -750,16 +749,16 @@ }, { "name": "php-stubs/acf-pro-stubs", - "version": "v6.0.6", + "version": "v6.1.7", "source": { "type": "git", "url": "https://github.com/php-stubs/acf-pro-stubs.git", - "reference": "3301f61c975ee6078fc89c1f6ba70358349ea137" + "reference": "0e6c6da1c581b7a6fa555da1ccd90260b5bbc495" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/acf-pro-stubs/zipball/3301f61c975ee6078fc89c1f6ba70358349ea137", - "reference": "3301f61c975ee6078fc89c1f6ba70358349ea137", + "url": "https://api.github.com/repos/php-stubs/acf-pro-stubs/zipball/0e6c6da1c581b7a6fa555da1ccd90260b5bbc495", + "reference": "0e6c6da1c581b7a6fa555da1ccd90260b5bbc495", "shasum": "" }, "require": { @@ -788,22 +787,22 @@ ], "support": { "issues": "https://github.com/php-stubs/acf-pro-stubs/issues", - "source": "https://github.com/php-stubs/acf-pro-stubs/tree/v6.0.6" + "source": "https://github.com/php-stubs/acf-pro-stubs/tree/v6.1.7" }, - "time": "2022-12-25T22:14:41+00:00" + "time": "2023-07-21T09:18:17+00:00" }, { "name": "php-stubs/woocommerce-stubs", - "version": "v7.9.0", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/php-stubs/woocommerce-stubs.git", - "reference": "3a2f522e29451490c357af550227795d2b0fc55a" + "reference": "f5a8621ca0a28c93b37b827ef92e7b49ad3d1923" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/woocommerce-stubs/zipball/3a2f522e29451490c357af550227795d2b0fc55a", - "reference": "3a2f522e29451490c357af550227795d2b0fc55a", + "url": "https://api.github.com/repos/php-stubs/woocommerce-stubs/zipball/f5a8621ca0a28c93b37b827ef92e7b49ad3d1923", + "reference": "f5a8621ca0a28c93b37b827ef92e7b49ad3d1923", "shasum": "" }, "require": { @@ -832,27 +831,27 @@ ], "support": { "issues": "https://github.com/php-stubs/woocommerce-stubs/issues", - "source": "https://github.com/php-stubs/woocommerce-stubs/tree/v7.9.0" + "source": "https://github.com/php-stubs/woocommerce-stubs/tree/v8.1.0" }, - "time": "2023-07-17T22:41:38+00:00" + "time": "2023-09-12T20:25:04+00:00" }, { "name": "php-stubs/wordpress-stubs", - "version": "v6.2.1", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "0009429e639b748eef1c955200ea0d4e5ad5627d" + "reference": "adda7609e71d5f4dc7b87c74f8ec9e3437d2e92c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/0009429e639b748eef1c955200ea0d4e5ad5627d", - "reference": "0009429e639b748eef1c955200ea0d4e5ad5627d", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/adda7609e71d5f4dc7b87c74f8ec9e3437d2e92c", + "reference": "adda7609e71d5f4dc7b87c74f8ec9e3437d2e92c", "shasum": "" }, "require-dev": { - "nikic/php-parser": "< 4.12.0", - "php": "~7.3 || ~8.0", + "nikic/php-parser": "^4.13", + "php": "^7.4 || ~8.0.0", "php-stubs/generator": "^0.8.3", "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpstan": "^1.10.12", @@ -860,7 +859,6 @@ }, "suggest": { "paragonie/sodium_compat": "Pure PHP implementation of libsodium", - "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" }, "type": "library", @@ -877,9 +875,9 @@ ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.2.1" + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.3.0" }, - "time": "2023-05-18T04:35:23+00:00" + "time": "2023-08-10T16:34:11+00:00" }, { "name": "phpcompatibility/php-compatibility", @@ -945,16 +943,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.32", + "version": "1.10.36", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44" + "reference": "ffa3089511121a672e62969404e4fddc753f9b15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c47e47d3ab03137c0e121e77c4d2cb58672f6d44", - "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ffa3089511121a672e62969404e4fddc753f9b15", + "reference": "ffa3089511121a672e62969404e4fddc753f9b15", "shasum": "" }, "require": { @@ -1003,7 +1001,7 @@ "type": "tidelift" } ], - "time": "2023-08-24T21:54:50+00:00" + "time": "2023-09-29T14:07:45+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/inc/class-blocks-css.php b/inc/class-blocks-css.php index c99a6421e..5602cd9df 100644 --- a/inc/class-blocks-css.php +++ b/inc/class-blocks-css.php @@ -123,7 +123,7 @@ function_exists( 'wp_is_block_theme' ) && $templates_parts = get_block_templates( array( 'slugs__in' => $slugs ), 'wp_template_part' ); foreach ( $templates_parts as $templates_part ) { - if ( isset( $templates_part->content ) && isset( $templates_part->slug ) && in_array( $templates_part->slug, $slugs ) ) { + if ( ! empty( $templates_part->content ) && ! empty( $templates_part->slug ) && in_array( $templates_part->slug, $slugs ) ) { $content .= $templates_part->content; } } diff --git a/inc/class-main.php b/inc/class-main.php index f2518a4eb..b42129a89 100644 --- a/inc/class-main.php +++ b/inc/class-main.php @@ -7,6 +7,7 @@ namespace ThemeIsle\GutenbergBlocks; +use ThemeIsle\GutenbergBlocks\Plugins\LimitedOffers; use ThemeIsle\GutenbergBlocks\Server\Dashboard_Server; /** @@ -44,6 +45,7 @@ public function init() { } add_filter( 'otter_blocks_about_us_metadata', array( $this, 'about_page' ) ); + } /** diff --git a/inc/class-pro.php b/inc/class-pro.php index 3b1e24e32..56dfd0478 100644 --- a/inc/class-pro.php +++ b/inc/class-pro.php @@ -7,6 +7,8 @@ namespace ThemeIsle\GutenbergBlocks; +use ThemeIsle\GutenbergBlocks\Plugins\LimitedOffers; + /** * Class Pro */ @@ -51,6 +53,8 @@ public function init_upsells() { add_action( 'otter_montly_scheduled_events', array( $this, 'reset_dashboard_notice' ) ); add_action( 'admin_init', array( $this, 'should_show_dashboard_upsell' ), 11 ); add_filter( 'plugin_action_links_' . plugin_basename( OTTER_BLOCKS_BASEFILE ), array( $this, 'add_pro_link' ) ); + + add_action( 'admin_init', array( $this, 'load_offers' ), 11 ); } /** @@ -180,6 +184,12 @@ public function should_show_dashboard_upsell() { $show_upsell = true; } + $offers = new LimitedOffers(); + + if ( $offers->is_active() ) { + $show_upsell = false; + } + if ( $show_upsell ) { add_action( 'admin_notices', array( $this, 'dashboard_upsell_notice' ) ); add_action( 'wp_ajax_dismiss_otter_notice', array( $this, 'dismiss_dashboard_notice' ) ); @@ -416,6 +426,20 @@ public function add_pro_link( $links ) { return $links; } + /** + * Load offers. + * + * @return void + */ + public function load_offers() { + if ( ! self::is_pro_installed() ) { + $offer = new LimitedOffers(); + if ( $offer->can_show_dashboard_banner() && $offer->is_active() ) { + $offer->load_dashboard_hooks(); + } + } + } + /** * Singleton method. * diff --git a/inc/class-registration.php b/inc/class-registration.php index 7e9796229..b2b197f4e 100644 --- a/inc/class-registration.php +++ b/inc/class-registration.php @@ -8,6 +8,7 @@ namespace ThemeIsle\GutenbergBlocks; use ThemeIsle\GutenbergBlocks\Main, ThemeIsle\GutenbergBlocks\Pro, ThemeIsle\GutenbergBlocks\Plugins\Stripe_API; +use ThemeIsle\GutenbergBlocks\Plugins\LimitedOffers; /** * Class Registration. @@ -35,6 +36,13 @@ class Registration { */ public static $block_dependencies = array(); + /** + * The ids of the used widgets in the page. + * + * @var array + */ + public static $widget_used = array(); // TODO: Monitor all the rendered widgets and enqueue the assets. + /** * Flag to mark that the scripts which have loaded. * @@ -81,6 +89,7 @@ public function init() { add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) ); add_filter( 'render_block', array( $this, 'load_sticky' ), 900, 2 ); add_filter( 'render_block', array( $this, 'subscribe_fa' ), 10, 2 ); + add_filter( 'dynamic_sidebar_params', array( $this, 'watch_used_widgets' ), 9999 ); add_action( 'wp_footer', @@ -272,6 +281,7 @@ public function enqueue_block_editor_assets() { 'version' => OTTER_BLOCKS_VERSION, 'isRTL' => is_rtl(), 'highlightDynamicText' => get_option( 'themeisle_blocks_settings_highlight_dynamic', true ), + 'hasOpenAiKey' => ! empty( get_option( 'themeisle_open_ai_api_key' ) ), ) ); @@ -348,7 +358,15 @@ function ( $content ) { } if ( $has_widgets ) { - $this->enqueue_dependencies( 'widgets' ); + add_filter( + 'wp_footer', + function ( $content ) { + $this->enqueue_dependencies( 'widgets' ); + + return $content; + } + ); + } if ( function_exists( 'get_block_templates' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() && current_theme_supports( 'block-templates' ) ) { @@ -383,7 +401,7 @@ public function enqueue_dependencies( $post = null ) { $templates_parts = get_block_templates( array( 'slugs__in' => $slugs ), 'wp_template_part' ); foreach ( $templates_parts as $templates_part ) { - if ( isset( $templates_part->content ) && isset( $templates_part->slug ) && in_array( $templates_part->slug, $slugs ) ) { + if ( ! empty( $templates_part->content ) && ! empty( $templates_part->slug ) && in_array( $templates_part->slug, $slugs ) ) { $content .= $templates_part->content; } } @@ -968,25 +986,31 @@ public static function sticky_style() { * @return string */ public static function get_active_widgets_content() { + $content = ''; + + if ( 0 === count( self::$widget_used ) ) { + return $content; + } + global $wp_registered_widgets; - $content = ''; $valid_widgets = array(); $widget_data = get_option( 'widget_block', array() ); // Loop through all widgets, and add any that are active. foreach ( $wp_registered_widgets as $widget_name => $widget ) { - // Get the active sidebar the widget is located in. - $sidebar = is_active_widget( $widget['callback'], $widget['id'] ); + if ( ! in_array( $widget['id'], self::$widget_used, true ) ) { + continue; + } - if ( $sidebar && 'wp_inactive_widgets' !== $sidebar ) { - $key = $widget['params'][0]['number']; + $key = $widget['params'][0]['number']; - if ( isset( $widget_data[ $key ] ) ) { - $valid_widgets[] = (object) $widget_data[ $key ]; - } + if ( isset( $widget_data[ $key ] ) ) { + $valid_widgets[] = (object) $widget_data[ $key ]; } } + self::$widget_used = array(); + foreach ( $valid_widgets as $widget ) { if ( isset( $widget->content ) ) { $content .= $widget->content; @@ -996,6 +1020,20 @@ public static function get_active_widgets_content() { return $content; } + /** + * Watch and save the used widgets. + * + * @param array $params The widget params. + * @return mixed + */ + public function watch_used_widgets( $params ) { + if ( isset( $params[0]['widget_id'] ) && ! in_array( $params[0]['widget_id'], self::$widget_used ) ) { + self::$widget_used[] = $params[0]['widget_id']; + } + + return $params; + } + /** * The instance method for the static class. * Defines and returns the instance of the static class. diff --git a/inc/css/blocks/class-leaflet-map-css.php b/inc/css/blocks/class-leaflet-map-css.php index fe4b6d5f0..f503a48a6 100644 --- a/inc/css/blocks/class-leaflet-map-css.php +++ b/inc/css/blocks/class-leaflet-map-css.php @@ -40,7 +40,16 @@ public function render_css( $block ) { 'property' => '--height', 'value' => 'height', 'format' => function( $value, $attrs ) { - return is_numeric( $value ) ? $value . 'px' : $value; + + // Check if the value is a number. + if ( is_numeric( $value ) ) { + $suffix = substr( $value, -2 ); + if ( 'px' !== $suffix ) { + return $value . 'px'; + } + } + + return $value; }, ), array( diff --git a/inc/css/class-block-frontend.php b/inc/css/class-block-frontend.php index c6e8b6a7a..0ec65053c 100644 --- a/inc/css/class-block-frontend.php +++ b/inc/css/class-block-frontend.php @@ -596,7 +596,7 @@ public function enqueue_fse_css() { $templates_parts = get_block_templates( array( 'slugs__in' => $slugs ), 'wp_template_part' ); foreach ( $templates_parts as $templates_part ) { - if ( isset( $templates_part->content ) && isset( $templates_part->slug ) && in_array( $templates_part->slug, $slugs ) ) { + if ( ! empty( $templates_part->content ) && ! empty( $templates_part->slug ) && in_array( $templates_part->slug, $slugs ) ) { $content .= $templates_part->content; } } diff --git a/inc/plugins/class-dashboard.php b/inc/plugins/class-dashboard.php index 6284f49eb..ef3854bed 100644 --- a/inc/plugins/class-dashboard.php +++ b/inc/plugins/class-dashboard.php @@ -162,6 +162,8 @@ public function enqueue_options_assets() { wp_set_script_translations( 'otter-blocks-scripts', 'otter-blocks' ); + $offer = new LimitedOffers(); + wp_localize_script( 'otter-blocks-scripts', 'otterObj', @@ -175,6 +177,7 @@ public function enqueue_options_assets() { 'upgradeLink' => tsdk_utmify( Pro::get_url(), 'options', Pro::get_reference() ), 'docsLink' => Pro::get_docs_url(), 'showFeedbackNotice' => $this->should_show_feedback_notice(), + 'deal' => ! Pro::is_pro_installed() ? $offer->get_localized_data() : array(), ) ) ); diff --git a/inc/plugins/class-limited-offers.php b/inc/plugins/class-limited-offers.php new file mode 100644 index 000000000..c402743c5 --- /dev/null +++ b/inc/plugins/class-limited-offers.php @@ -0,0 +1,341 @@ + + */ + public $offer_metadata = array(); + + /** + * Timeline for the offers. + * + * @var array[] + */ + public $timelines = array( + 'bf' => array( + 'start' => '2023-11-20 00:00:00', + 'end' => '2023-11-27 23:59:00', + ), + ); + + /** + * LimitedOffers constructor. + */ + public function __construct() { + try { + if ( $this->is_deal_active( 'bf' ) ) { + $this->activate_bff(); + } + } catch ( Exception $e ) { + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + error_log( $e->getMessage() ); // phpcs:ignore + } + } + } + + /** + * Load hooks for the dashboard. + * + * @return void + */ + public function load_dashboard_hooks() { + add_filter( 'themeisle_products_deal_priority', array( $this, 'add_priority' ) ); + add_action( 'admin_notices', array( $this, 'render_notice' ) ); + add_action( 'wp_ajax_dismiss_themeisle_event_notice_otter', array( $this, 'disable_notification_ajax' ) ); + } + + /** + * Check if we have an active deal. + * + * @return bool True if the deal is active. + */ + public function is_active() { + return ! empty( $this->active ); + } + + /** + * Activate the Black Friday deal. + * + * @return void + */ + public function activate_bff() { + $this->active = 'bf'; + + $this->offer_metadata = array( + 'bannerUrl' => OTTER_BLOCKS_URL . 'assets/images/black-friday-banner.png', + 'bannerAlt' => 'Otter Black Friday Sale', + 'linkDashboard' => tsdk_utmify( 'https://themeisle.com/plugins/otter-blocks/blackfriday/', 'blackfridayltd23', 'dashboard' ), + 'linkGlobal' => tsdk_utmify( 'https://themeisle.com/plugins/otter-blocks/blackfriday/', 'blackfridayltd23', 'globalnotice' ), + ); + } + + /** + * Get the slug of the active deal. + * + * @return string Active deal. + */ + public function get_active_deal() { + return $this->active; + } + + /** + * Check if the deal is active with the given slug. + * + * @param string $slug Slug of the deal. + * + * @throws Exception When date is invalid. + */ + public function is_deal_active( $slug ) { + + if ( empty( $slug ) || ! array_key_exists( $slug, $this->timelines ) ) { + return false; + } + + return $this->check_date_range( $this->timelines[ $slug ]['start'], $this->timelines[ $slug ]['end'] ); + } + + /** + * Get the remaining time for the deal in a human readable format. + * + * @param string $slug Slug of the deal. + * @return string Remaining time for the deal. + */ + public function get_remaining_time_for_deal( $slug ) { + if ( empty( $slug ) || ! array_key_exists( $slug, $this->timelines ) ) { + return ''; + } + + try { + $end_date = new DateTime( $this->timelines[ $slug ]['end'], new DateTimeZone( 'GMT' ) ); + $current_date = new DateTime( 'now', new DateTimeZone( 'GMT' ) ); + $diff = $end_date->diff( $current_date ); + + if ( 0 < $diff->days ) { + return 1 === $diff->days ? $diff->format( '%a day' ) : $diff->format( '%a days' ); + } + + if ( 0 < $diff->h ) { + return 1 === $diff->h ? $diff->format( '%h hour' ) : $diff->format( '%h hours' ); + } + + if ( 0 < $diff->i ) { + return 1 === $diff->i ? $diff->format( '%i minute' ) : $diff->format( '%i minutes' ); + } + + return 1 === $diff->s ? $diff->format( '%s second' ) : $diff->format( '%s seconds' ); + } catch ( Exception $e ) { + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + error_log( $e->getMessage() ); // phpcs:ignore + } + } + + return ''; + } + + /** + * Check if the current date is in the range of the offer. + * + * @param string $start Start date. + * @param string $end End date. + * + * @throws Exception When date is invalid. + */ + public function check_date_range( $start, $end ) { + + $start_date = new DateTime( $start, new DateTimeZone( 'GMT' ) ); + $end_date = new DateTime( $end, new DateTimeZone( 'GMT' ) ); + $current_date = new DateTime( 'now', new DateTimeZone( 'GMT' ) ); + + return $start_date <= $current_date && $current_date <= $end_date; + } + + /** + * Get the localized data for the plugin. + * + * @return array Localized data. + */ + public function get_localized_data() { + return array_merge( + array( + 'active' => $this->is_active(), + 'dealSlug' => $this->get_active_deal(), + 'remainingTime' => $this->get_remaining_time_for_deal( $this->get_active_deal() ), + 'urgencyText' => 'Hurry Up! Only ' . $this->get_remaining_time_for_deal( $this->get_active_deal() ) . ' left', + ), + $this->offer_metadata + ); + } + + /** + * Disable the notification via ajax. + * + * @return void + */ + public function disable_notification_ajax() { + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'dismiss_themeisle_event_notice_otter' ) ) { + wp_die( esc_html( __( 'Invalid nonce! Refresh the page and try again.', 'otter-blocks' ) ) ); + } + + // We record the time and the plugin of the dismissed notification. + update_option( $this->wp_option_dismiss_notification_key_base . $this->active, 'otter_' . $this->active . '_' . current_time( 'Y_m_d' ) ); + wp_die( 'success' ); + } + + /** + * Render the dashboard banner. + * + * @return void + */ + public function render_notice() { + + if ( ! $this->has_priority() ) { + return; + } + + $message = 'Otter Black Friday Sale - Save big with a Lifetime License of Otter Pro Plan. Only 100 licenses, for a limited time!'; + + ?> + +
+
+ + + + + + + + + + + + + + + + + + +
+ + wp_option_dismiss_notification_key_base . $this->active, false ); + } + + /** + * Add product priority to the filter. + * + * @param array $products Registered products. + * @return array Array enhanced with Neve priority. + */ + public function add_priority( $products ) { + $products['otter'] = 1; + return $products; + } + + /** + * Check if the current product has priority. + * Use this for conditional rendering if you want to show the banner only for one product. + * + * @return bool True if the current product has priority. + */ + public function has_priority() { + $products = apply_filters( 'themeisle_products_deal_priority', array() ); + + if ( empty( $products ) ) { + return true; + } + + $highest_priority = array_search( min( $products ), $products, true ); + return 'otter' === $highest_priority; + } +} diff --git a/package-lock.json b/package-lock.json index 934266edc..8fac6c9e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,10 +6,10 @@ "packages": { "": { "name": "otter-blocks", - "version": "2.3.4", + "version": "2.4.0", "license": "GPL-2.0+", "dependencies": { - "@wordpress/icons": "^9.32.0", + "@wordpress/icons": "^9.33.0", "array-move": "^3.0.1", "classnames": "^2.3.1", "currency-symbol-map": "^5.0.1", @@ -18,7 +18,7 @@ "object-hash": "^3.0.0", "react-sortable-hoc": "^2.0.0", "react-visibility-sensor": "^5.1.1", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "devDependencies": { "@automattic/babel-plugin-replace-textdomain": "^1.0.26", @@ -34,14 +34,14 @@ "@types/wordpress__components": "^23.0.1", "@typescript-eslint/parser": "^6.3.0", "@wordpress/block-editor": "^12.9.0", - "@wordpress/components": "^25.7.0", + "@wordpress/components": "^25.9.0", "@wordpress/compose": "^6.15.0", - "@wordpress/data": "^9.7.0", - "@wordpress/dom-ready": "^3.41.0", + "@wordpress/data": "^9.12.0", + "@wordpress/dom-ready": "^3.43.0", "@wordpress/e2e-test-utils": "^10.4.0", "@wordpress/e2e-test-utils-playwright": "^0.9.0", "@wordpress/e2e-tests": "^7.9.0", - "@wordpress/element": "^5.18.0", + "@wordpress/element": "^5.20.0", "@wordpress/env": "^8.7.0", "@wordpress/scripts": "^26.9.0", "conventional-changelog-simple-preset": "^1.0.20", @@ -86,18 +86,18 @@ } }, "node_modules/@ariakit/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.2.7.tgz", - "integrity": "sha512-Hs0N1EMYq88WW4v9xnSIHNR38TvbQuoUX6FYFmeLCZSTIXQBiET7lr1DQXwOOmdEtRtlxQ5HsxbTkxeOkPv+eg==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.3.tgz", + "integrity": "sha512-8x77R0aE9O9pheygg+h/z0oU9Wx/Xdlr7nfkl4klGnkJma8/nAhJ2RrchCTQCUef4WMsRnq/doCz8m/sslP6CA==", "dev": true }, "node_modules/@ariakit/react": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.2.12.tgz", - "integrity": "sha512-4rAgMyUURHW78EKgRCanhyRUtsiYCOxO65BBHF4mg3tZsDeOvu9kBG5IAXX8mUgakTcyr0EKXuOtGThaj7gobA==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.4.tgz", + "integrity": "sha512-dgu98m9kfkcG9oqazPCsIu29Na2WaQVeq3i8iW1JUQ4PMGsIu2EerDGjhKVrvFb5URd+pipy9XHmg7AoqvMHjw==", "dev": true, "dependencies": { - "@ariakit/react-core": "0.2.12" + "@ariakit/react-core": "0.3.4" }, "funding": { "type": "opencollective", @@ -109,12 +109,12 @@ } }, "node_modules/@ariakit/react-core": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.2.12.tgz", - "integrity": "sha512-3KSKlX10nnhCvjsbPW0CAnqG+6grryfwnMkeJJ/h34FSV7hEfUMexmIjKBVZyfBG08Xj8NjSK8kkx9c3ChkXeA==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.4.tgz", + "integrity": "sha512-kTRzbSZjRIUsLKQpjyAlzQGI+H01UBzHMKErk2Nag+Ure6m8aNHhD0TlOFyW4Bsf5NVsdHrXAVKNfCfJwk7eVg==", "dev": true, "dependencies": { - "@ariakit/core": "0.2.7", + "@ariakit/core": "0.3.3", "@floating-ui/dom": "^1.0.0", "use-sync-external-store": "^1.2.0" }, @@ -5087,6 +5087,18 @@ "@types/node": "*" } }, + "node_modules/@types/gradient-parser": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@types/gradient-parser/-/gradient-parser-0.1.3.tgz", + "integrity": "sha512-XDbrTSBlQV9nxE1GiDL3FaOPy4G/KaJkhDutBX48Kg8CYZMBARyyDFGCWfWJn4pobmInmwud1xxH7VJMAr0CKQ==", + "dev": true + }, + "node_modules/@types/highlight-words-core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/highlight-words-core/-/highlight-words-core-1.2.1.tgz", + "integrity": "sha512-9VZUA5omXBfn+hDxFjUDu1FOJTBM3LmvqfDey+Z6Aa8B8/JmF5SMj6FBrjfgJ/Q3YXOZd3qyTDfJyMZSs/wCUA==", + "dev": true + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", @@ -6176,14 +6188,14 @@ } }, "node_modules/@wordpress/a11y": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.41.0.tgz", - "integrity": "sha512-T+7rHdX4k72ty3MdtySuL41d4wrIXRKdM1Xhjr89G/OXyetsY0nb4n6jUsFGnf69iq7gFSgVjEVogbOc/ffbTA==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.43.0.tgz", + "integrity": "sha512-jtBGQnjabqRIPyXSSjP2YXXD5qCY3FW2742hQo2yBrKWCwGrP2t7sdR6P6nAlRDKPMMrecK+RsyW3KKTgNgFzQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/dom-ready": "^3.41.0", - "@wordpress/i18n": "^4.41.0" + "@wordpress/dom-ready": "^3.43.0", + "@wordpress/i18n": "^4.43.0" }, "engines": { "node": ">=12" @@ -6383,6 +6395,15 @@ "react": "^18.0.0" } }, + "node_modules/@wordpress/blocks/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@wordpress/browserslist-config": { "version": "5.21.0", "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.21.0.tgz", @@ -6419,12 +6440,12 @@ } }, "node_modules/@wordpress/components": { - "version": "25.7.0", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-25.7.0.tgz", - "integrity": "sha512-CFUhdMKuIA+0flsA3ACnJ0h84uKo+PrGkWG3nCSJwifdC6AVB7b7VOriDRhYsJXKoyr3QV+gRfAnFn0Hrc7tQQ==", + "version": "25.9.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-25.9.0.tgz", + "integrity": "sha512-UA7oxtxoM0POdHwFbtlwotABGXeXB8Xy/5cAElga+MjVGRNGL6nrpkQgBUwXALvR39she25t1TJKM+OwxQ5ogg==", "dev": true, "dependencies": { - "@ariakit/react": "^0.2.12", + "@ariakit/react": "^0.3.3", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -6434,24 +6455,26 @@ "@emotion/utils": "^1.0.0", "@floating-ui/react-dom": "^2.0.1", "@radix-ui/react-dropdown-menu": "2.0.4", + "@types/gradient-parser": "0.1.3", + "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.2.24", - "@wordpress/a11y": "^3.41.0", - "@wordpress/compose": "^6.18.0", - "@wordpress/date": "^4.41.0", - "@wordpress/deprecated": "^3.41.0", - "@wordpress/dom": "^3.41.0", - "@wordpress/element": "^5.18.0", - "@wordpress/escape-html": "^2.41.0", - "@wordpress/hooks": "^3.41.0", - "@wordpress/html-entities": "^3.41.0", - "@wordpress/i18n": "^4.41.0", - "@wordpress/icons": "^9.32.0", - "@wordpress/is-shallow-equal": "^4.41.0", - "@wordpress/keycodes": "^3.41.0", - "@wordpress/primitives": "^3.39.0", - "@wordpress/private-apis": "^0.23.0", - "@wordpress/rich-text": "^6.18.0", - "@wordpress/warning": "^2.41.0", + "@wordpress/a11y": "^3.43.0", + "@wordpress/compose": "^6.20.0", + "@wordpress/date": "^4.43.0", + "@wordpress/deprecated": "^3.43.0", + "@wordpress/dom": "^3.43.0", + "@wordpress/element": "^5.20.0", + "@wordpress/escape-html": "^2.43.0", + "@wordpress/hooks": "^3.43.0", + "@wordpress/html-entities": "^3.43.0", + "@wordpress/i18n": "^4.43.0", + "@wordpress/icons": "^9.34.0", + "@wordpress/is-shallow-equal": "^4.43.0", + "@wordpress/keycodes": "^3.43.0", + "@wordpress/primitives": "^3.41.0", + "@wordpress/private-apis": "^0.25.0", + "@wordpress/rich-text": "^6.20.0", + "@wordpress/warning": "^2.43.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", @@ -6471,7 +6494,7 @@ "reakit": "^1.3.11", "remove-accents": "^0.5.0", "use-lilius": "^2.0.1", - "uuid": "^8.3.0", + "uuid": "^9.0.1", "valtio": "1.7.0" }, "engines": { @@ -6482,20 +6505,33 @@ "react-dom": "^18.0.0" } }, + "node_modules/@wordpress/components/node_modules/@wordpress/private-apis": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.25.0.tgz", + "integrity": "sha512-y1+U+wAwcjQ5QTDEDGvJbcc0VhyFx0tJh61i6eeBYy2mFXRGJDHyQVjp3agO8YXQdyHeAusVVOKTMT7mZdzUMw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.16.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wordpress/compose": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-6.18.0.tgz", - "integrity": "sha512-aZyvttCnT8HI4vS8Nb8y5Go+AycrThy0gvEl9jc9ZB9emm1ZifNkA6gTNpBd7zU2uzS4wUpiZYGGvUNeAuShvQ==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-6.20.0.tgz", + "integrity": "sha512-9BOECyaz9HN7v98Lz6sAJxemcR6rnUZS2DAoGVJHxUPqYiyIRIMEax5SR6MVI/Qr1n7rFouK/0lUP7ZGRzQauA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "^3.41.0", - "@wordpress/dom": "^3.41.0", - "@wordpress/element": "^5.18.0", - "@wordpress/is-shallow-equal": "^4.41.0", - "@wordpress/keycodes": "^3.41.0", - "@wordpress/priority-queue": "^2.41.0", + "@wordpress/deprecated": "^3.43.0", + "@wordpress/dom": "^3.43.0", + "@wordpress/element": "^5.20.0", + "@wordpress/is-shallow-equal": "^4.43.0", + "@wordpress/keycodes": "^3.43.0", + "@wordpress/priority-queue": "^2.43.0", + "@wordpress/undo-manager": "^0.3.0", "change-case": "^4.1.2", "clipboard": "^2.0.8", "mousetrap": "^1.6.5", @@ -6509,25 +6545,25 @@ } }, "node_modules/@wordpress/data": { - "version": "9.11.0", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.11.0.tgz", - "integrity": "sha512-1UaummqcfxO4EU7eKfkxEaRjlZjRvqHRdtdjyBtrpWHxIaLrTzBdM7rBNu06tilZBoAXGRoBc5TyPuvNI3IWNg==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.13.0.tgz", + "integrity": "sha512-SayS6JsOFL2SBKIk4NFlmGLYvH7ocqOf6iuMq2aJ8E04VuExLEDqnDjxXkdOgLrrie6dOLG21EvNRk7Vc7QDOA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/compose": "^6.18.0", - "@wordpress/deprecated": "^3.41.0", - "@wordpress/element": "^5.18.0", - "@wordpress/is-shallow-equal": "^4.41.0", - "@wordpress/priority-queue": "^2.41.0", - "@wordpress/private-apis": "^0.23.0", - "@wordpress/redux-routine": "^4.41.0", + "@wordpress/compose": "^6.20.0", + "@wordpress/deprecated": "^3.43.0", + "@wordpress/element": "^5.20.0", + "@wordpress/is-shallow-equal": "^4.43.0", + "@wordpress/priority-queue": "^2.43.0", + "@wordpress/private-apis": "^0.25.0", + "@wordpress/redux-routine": "^4.43.0", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", "is-plain-object": "^5.0.0", "is-promise": "^4.0.0", "redux": "^4.1.2", - "turbo-combine-reducers": "^1.0.2", + "rememo": "^4.0.2", "use-memo-one": "^1.1.1" }, "engines": { @@ -6537,14 +6573,26 @@ "react": "^18.0.0" } }, + "node_modules/@wordpress/data/node_modules/@wordpress/private-apis": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.25.0.tgz", + "integrity": "sha512-y1+U+wAwcjQ5QTDEDGvJbcc0VhyFx0tJh61i6eeBYy2mFXRGJDHyQVjp3agO8YXQdyHeAusVVOKTMT7mZdzUMw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.16.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wordpress/date": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-4.41.0.tgz", - "integrity": "sha512-R0cTQKev7xT/srjAgUJOgW7CYnXuRdxSpXG7timYr3jEqgYoJFSAP2miq3+FWWSra1nesdT+r4o5z+3rIKFLLg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-4.43.0.tgz", + "integrity": "sha512-fXHXewPYogPGsQSzy1z9WIUluWof2vPkRc4KXE5vgmWaUbl5xvPEiRX4ZJcofYmKzk0ioDYrq56eDKvEgKoF+A==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/deprecated": "^3.41.0", + "@wordpress/deprecated": "^3.43.0", "moment": "^2.29.4", "moment-timezone": "^0.5.40" }, @@ -6569,35 +6617,35 @@ } }, "node_modules/@wordpress/deprecated": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-3.41.0.tgz", - "integrity": "sha512-8so9fJC6MvMeMxaRqEJYtzXm1RP2i+nq3NXG9DW4fbo8ICEIe1QqBpCFqV4FbkHs8PRqyJ8IJ7C6NnAvL3BWKw==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-3.43.0.tgz", + "integrity": "sha512-rmnehCKUEvZBH1VbQiK7YV9Yh5EQPcOO0kaf7UIGXUrH/pEpUn1PW+5Yox4ZyVNvqCQj2mbs6sqSNiIsjRWpAA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.41.0" + "@wordpress/hooks": "^3.43.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/dom": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-3.41.0.tgz", - "integrity": "sha512-0FKG4c33G7jOElzy1adqgNjowYBaWX98Q99X9WjpjtF+AY7/Sljq4zOGh5iZXSM/1dpKLPs4f/frFEqueq6MGg==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-3.43.0.tgz", + "integrity": "sha512-fTaZvTG/0En5r+ArMOGiBUomhbpJJf2RYMMu2ok39E/alwIawZHz/qeL3706BlT4r2QPCfK/tUOHQSh13+3ocg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/deprecated": "^3.41.0" + "@wordpress/deprecated": "^3.43.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/dom-ready": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.41.0.tgz", - "integrity": "sha512-xbTCYC1192nUiBLB384GrzlQMbFnSv31TjI4+Y4JhR46+alcSsF2KPmlR8htxRbAXBx7z8lT78Fv+0ULhSIERA==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.43.0.tgz", + "integrity": "sha512-XNlkKi9BwsvyUgTFWZbwywknagc26UmdeqaDHP1l+M3ztKw7V9FCZxyXgM9M+i0jNReREzK3EzjjG+nNzFb0+Q==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -6677,15 +6725,24 @@ "react-dom": "^18.0.0" } }, + "node_modules/@wordpress/e2e-tests/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@wordpress/element": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.18.0.tgz", - "integrity": "sha512-OynuZuTFdmterh/ASmMSSKjdBj5r1hcwQi37AQnp7+GpyIV3Ol5PR4UWWYB0coW0Gkd0giJkQAwC71/ZkEPYqQ==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.20.0.tgz", + "integrity": "sha512-EO2cXUTrACQJ0JG+nuQnnaKu/qJbnBPmTgy6HCfU90um1G3kdG/iHh+T1YCi/WqxW+6UrL7I8FbxPfcEJvvONA==", "dependencies": { "@babel/runtime": "^7.16.0", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", - "@wordpress/escape-html": "^2.41.0", + "@wordpress/escape-html": "^2.43.0", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.2.0", @@ -6719,9 +6776,9 @@ } }, "node_modules/@wordpress/escape-html": { - "version": "2.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.41.0.tgz", - "integrity": "sha512-fFDuAO/csLVemQrJKTrwxjmR7d2a6zEuDVCKi2jUt7j9rpLpz9IZnEVD2q/icOj2+u6joeDwvCyyPyTreqEZHA==", + "version": "2.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.43.0.tgz", + "integrity": "sha512-+hLbsx4PXGnziUSS/7W9B47xgh2VUg11rSbe3Q3bTMCFWaYqk0cvAk5C74Re99Hiekct99HmpnmyVq+we8bkUQ==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -6885,9 +6942,9 @@ } }, "node_modules/@wordpress/hooks": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.41.0.tgz", - "integrity": "sha512-o3fC6Z0kCLzZNFUT5W7C1d2mR+qjVwLaWjrwuJngJG92wly4IzKgAUDs/iJZojxtePFMP8JOFCg4FMuzG/VhWw==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.43.0.tgz", + "integrity": "sha512-SHSiyFUEsggihl0pDvY1l72q+fHMDyFHtIR3GCt0uV2ifctvoa/PIYdVwrxpGQaGdNEV25XCZ4kNldqJmfTddw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -6897,9 +6954,9 @@ } }, "node_modules/@wordpress/html-entities": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-3.41.0.tgz", - "integrity": "sha512-nb8ioGMi9yBfOzy2tIvIKQA+MaVNbgTflM/uiwhb5D/KTqtIUp+e84BOCOOZDI7xtWKQKhtGAn126Ko7hi73Jg==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-3.43.0.tgz", + "integrity": "sha512-e7OJU2DRa2Z6TxLq5y2/jKVjlqdVJDwwR0yNp4ajyrtGIMNJw+7PXvdgSoroD5M6UjWRuLT57crcgVT4M53nRA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -6909,13 +6966,13 @@ } }, "node_modules/@wordpress/i18n": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.41.0.tgz", - "integrity": "sha512-MXA+DiVSF2CS0ZhFEBq/eJjfHuKMcu3FUuiF/Dpc1YZRD1X7N6xPlfo4xKJZVUWIAsfgpc8oA2YMLw1VTFzrRA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.43.0.tgz", + "integrity": "sha512-XHU/vGgI+pgjJU9WzWDHke1u948z8i3OPpKUNdxc/gMcTkKaKM4D8DW1+VMSQHyU6pneP8+ph7EF+1RIehP3lQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.41.0", + "@wordpress/hooks": "^3.43.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "sprintf-js": "^1.1.1", @@ -6929,22 +6986,22 @@ } }, "node_modules/@wordpress/icons": { - "version": "9.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.32.0.tgz", - "integrity": "sha512-67imRDf5LF6v4vCBmvAEa4ZzcH0jvwIaBr1Y2ou4ElVJ1KZjiu1C93Lp6dcAy/08L8eX+eZZCwTrTYI+tl9v5w==", + "version": "9.34.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.34.0.tgz", + "integrity": "sha512-yJWfvgnWnPBd/Co7VjdbCj3o2HJdP4npblJCj7LR+KcnCTPYZWMc4GU3OuzmxMByALuAndfIxgm1YDSHt/F4Wg==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.18.0", - "@wordpress/primitives": "^3.39.0" + "@wordpress/element": "^5.20.0", + "@wordpress/primitives": "^3.41.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/is-shallow-equal": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.41.0.tgz", - "integrity": "sha512-z0+bdJvjOcbPf7vGiC0Zfoff+2WyFRFwsJex9d9N9CTVvr87kaVHBZuZlPX6iFmS22RzM2tLeppBKlOSRtSI4w==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.43.0.tgz", + "integrity": "sha512-KhEvz2V9gjq+C8v/YEYMRetOg2YI/Ik8cc8dWlOMHp7GbZAPHACplR4ZeEF8Ef2LZ8JnNL6IqW8zZzUhSOxlqg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -7028,13 +7085,13 @@ } }, "node_modules/@wordpress/keycodes": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.41.0.tgz", - "integrity": "sha512-0DY07PV5qATrvWLc5jWgrgUGpeFrqvY+K0qF0gLDw1rpIZBxLre+N3K7ANC/iZzSsPLbYxxVF0SSFDIVI23Pug==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.43.0.tgz", + "integrity": "sha512-B6rYPiKFdQTlnJfm93R+usQnjEODUX/K4+hMvY5ZZOinvxe7KyU/xyFGz7gRrS8WmIEYcJowqSmAlGgVs4XwKQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.41.0", + "@wordpress/i18n": "^4.43.0", "change-case": "^4.1.2" }, "engines": { @@ -7122,12 +7179,12 @@ } }, "node_modules/@wordpress/primitives": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.39.0.tgz", - "integrity": "sha512-QvtVJFQGCOEwdXIpXqktz31p327lj/5n4iwbIlOaGf0AZCPA8974m+O/wFkQCskGOJ9tVRveqZtH60aqKH/ffA==", + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.41.0.tgz", + "integrity": "sha512-md+4T5VWkWOLSa0p7rBPnKppa30EgYYAdfvqDXLzZTcaO1ZNJAJM3VtLkQPN2qsvLjy3ToT1ZmGF0jsoCP2bAg==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.18.0", + "@wordpress/element": "^5.20.0", "classnames": "^2.3.1" }, "engines": { @@ -7135,9 +7192,9 @@ } }, "node_modules/@wordpress/priority-queue": { - "version": "2.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.41.0.tgz", - "integrity": "sha512-OntRPhdybFO5da+MO0/pvnRYG+D+c1kU0KRPDuEa+ArZmmQ/lQC+z+/du9nP/cgvQCAzLHJo2/VKCSQZVyewKw==", + "version": "2.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.43.0.tgz", + "integrity": "sha512-jDsiF+3rMm6jme61AUHxHEIF1gfkCptO/MUdzh7uzyd5ZFnYG8kz+0zU2VX2bXlbqWHYmTsYr446rxTl7nkqRQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -7160,9 +7217,9 @@ } }, "node_modules/@wordpress/redux-routine": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-4.41.0.tgz", - "integrity": "sha512-u5MVqtcChtHOqy3Fk5ml0HPBF13uHoz6ca1XvzstHKOiMWi4xNj6Nn2YgnscfvsxGxxjQz2su1Qcs0SGZI0g+w==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-4.43.0.tgz", + "integrity": "sha512-Tc7Q2QgHm+KrWKWCATX+FlaZGaTK/xTFY1m51iWoAJFzvlIoiFOfoVH2FHDppxnxCrvNrRdu2FTagoxx9UzpLw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -7178,20 +7235,20 @@ } }, "node_modules/@wordpress/rich-text": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.18.0.tgz", - "integrity": "sha512-BuBb/yWFLq+/joYI3XMYyF7MQDVYGh8V4AB0+v4mHH+7cTecSOOilUAW/JlEXqXS3LhvghabBk157UaH1gh8IA==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.20.0.tgz", + "integrity": "sha512-MRjSVm6OE4xkgQRIXx+PJUZk9FKJbFVfpSRQDyhjppMDTIxaxPYyiGIkTa3yxTmk1OZu8yfF5eE6oxzxoN1vjg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.41.0", - "@wordpress/compose": "^6.18.0", - "@wordpress/data": "^9.11.0", - "@wordpress/deprecated": "^3.41.0", - "@wordpress/element": "^5.18.0", - "@wordpress/escape-html": "^2.41.0", - "@wordpress/i18n": "^4.41.0", - "@wordpress/keycodes": "^3.41.0", + "@wordpress/a11y": "^3.43.0", + "@wordpress/compose": "^6.20.0", + "@wordpress/data": "^9.13.0", + "@wordpress/deprecated": "^3.43.0", + "@wordpress/element": "^5.20.0", + "@wordpress/escape-html": "^2.43.0", + "@wordpress/i18n": "^4.43.0", + "@wordpress/keycodes": "^3.43.0", "memize": "^2.1.0", "rememo": "^4.0.2" }, @@ -7467,6 +7524,19 @@ "node": ">=12" } }, + "node_modules/@wordpress/undo-manager": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-0.3.0.tgz", + "integrity": "sha512-buZRSisLRgQKJrhr7c1FSydrWgHEH/0AxlEJ9gqIjsUHsG6D39Cx6RcZYX5eW5NBL3nTDVeVD4STNyHGNA+ZdQ==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.16.0", + "@wordpress/is-shallow-equal": "^4.43.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wordpress/url": { "version": "3.42.0", "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.42.0.tgz", @@ -7481,9 +7551,9 @@ } }, "node_modules/@wordpress/warning": { - "version": "2.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.41.0.tgz", - "integrity": "sha512-kSqx1z7MaNjNFg+/b5H9oY5D6hYpsKPvaonpk5CADTXOoWoEdSSkwDnCZWxaXu3Kgz4qB5EmaC4D3bKTFkFldw==", + "version": "2.43.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.43.0.tgz", + "integrity": "sha512-LPTc3Vf3Vy9DpL1foC49PCeGKnid9JdahPoaZhFX/WMG1VLelLBEwSLln/x7cq6c+8e8xbbex02t5IhbtLiEIw==", "dev": true, "engines": { "node": ">=12" @@ -26155,6 +26225,15 @@ "node": ">=0.8.0" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -28335,9 +28414,13 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } diff --git a/package.json b/package.json index 2cdc6d551..0770d1694 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "lasttranslator": "Themeisle Translate Team " }, "dependencies": { - "@wordpress/icons": "^9.32.0", + "@wordpress/icons": "^9.33.0", "array-move": "^3.0.1", "classnames": "^2.3.1", "currency-symbol-map": "^5.0.1", @@ -63,7 +63,7 @@ "object-hash": "^3.0.0", "react-sortable-hoc": "^2.0.0", "react-visibility-sensor": "^5.1.1", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "devDependencies": { "@automattic/babel-plugin-replace-textdomain": "^1.0.26", @@ -79,14 +79,14 @@ "@types/wordpress__components": "^23.0.1", "@typescript-eslint/parser": "^6.3.0", "@wordpress/block-editor": "^12.9.0", - "@wordpress/components": "^25.7.0", + "@wordpress/components": "^25.9.0", "@wordpress/compose": "^6.15.0", - "@wordpress/data": "^9.7.0", - "@wordpress/dom-ready": "^3.41.0", + "@wordpress/data": "^9.12.0", + "@wordpress/dom-ready": "^3.43.0", "@wordpress/e2e-test-utils": "^10.4.0", "@wordpress/e2e-test-utils-playwright": "^0.9.0", "@wordpress/e2e-tests": "^7.9.0", - "@wordpress/element": "^5.18.0", + "@wordpress/element": "^5.20.0", "@wordpress/env": "^8.7.0", "@wordpress/scripts": "^26.9.0", "conventional-changelog-simple-preset": "^1.0.20", diff --git a/plugins/otter-pro/inc/plugins/class-live-search.php b/plugins/otter-pro/inc/plugins/class-live-search.php index 00318fda0..c43c76216 100644 --- a/plugins/otter-pro/inc/plugins/class-live-search.php +++ b/plugins/otter-pro/inc/plugins/class-live-search.php @@ -23,6 +23,7 @@ class Live_Search { */ public function init() { add_filter( 'render_block', array( $this, 'render_blocks' ), 10, 2 ); + add_action( 'otter_load_live_search_deps', array( $this, 'load_deps' ) ); } /** @@ -39,6 +40,44 @@ public function render_blocks( $block_content, $block ) { return $block_content; } + do_action( 'otter_load_live_search_deps' ); + + $post_types_data = ''; + if ( isset( $block['attrs']['otterSearchQuery']['post_type'] ) ) { + $post_types_data = 'data-post-types=' . wp_json_encode( $block['attrs']['otterSearchQuery']['post_type'] ); + } + + // Insert hidden fields to filter core's search results. + $query_params_markup = ''; + if ( isset( $block['attrs']['otterSearchQuery'] ) && count( $block['attrs']['otterSearchQuery'] ) > 0 ) { + foreach ( $block['attrs']['otterSearchQuery'] as $param => $value ) { + $query_params_markup .= sprintf( + '', + esc_attr( $param ), + esc_attr( implode( ',', $value ) ) + ); + } + } + + $block_content = substr( $block_content, 0, strpos( $block_content, '' ) ) . $query_params_markup . substr( $block_content, strpos( $block_content, '' ) ); + return ''; + } + + /** + * Load the live search dependencies. + * + * @return void + * + * @static + */ + public static function load_deps() { + + $has_license = in_array( License::get_license_type(), array( 2, 3 ) ) || ( License::has_active_license() && isset( License::get_license_data()->otter_pro ) ); + + if ( ! $has_license ) { + return; + } + $asset_file = include OTTER_BLOCKS_PATH . '/build/blocks/live-search.asset.php'; wp_enqueue_script( 'otter-live-search', @@ -64,26 +103,6 @@ public function render_blocks( $block_content, $block ) { $asset_file = include OTTER_BLOCKS_PATH . '/build/blocks/live-search-style.asset.php'; wp_enqueue_style( 'otter-live-search-style', OTTER_BLOCKS_URL . 'build/blocks/live-search-style.css', $asset_file['dependencies'], $asset_file['version'] ); - - $post_types_data = ''; - if ( isset( $block['attrs']['otterSearchQuery']['post_type'] ) ) { - $post_types_data = 'data-post-types=' . wp_json_encode( $block['attrs']['otterSearchQuery']['post_type'] ); - } - - // Insert hidden fields to filter core's search results. - $query_params_markup = ''; - if ( isset( $block['attrs']['otterSearchQuery'] ) && count( $block['attrs']['otterSearchQuery'] ) > 0 ) { - foreach ( $block['attrs']['otterSearchQuery'] as $param => $value ) { - $query_params_markup .= sprintf( - '', - esc_attr( $param ), - esc_attr( implode( ',', $value ) ) - ); - } - } - - $block_content = substr( $block_content, 0, strpos( $block_content, '' ) ) . $query_params_markup . substr( $block_content, strpos( $block_content, '' ) ); - return ''; } /** diff --git a/src/animation/frontend/typing/index.js b/src/animation/frontend/typing/index.js index b250f1225..cd4fae4fd 100644 --- a/src/animation/frontend/typing/index.js +++ b/src/animation/frontend/typing/index.js @@ -109,16 +109,15 @@ domReady( () => { .o-anim-typing-caret::after { font-weight: 100; content: '|'; - color: #2E3D48; animation: 1s blink step-end infinite; } - + @keyframes blink { from, to { color: transparent; } 50% { - color: black; + color: inherit; } } `; diff --git a/src/blocks/blocks/advanced-heading/edit.js b/src/blocks/blocks/advanced-heading/edit.js index a2190cee2..dc4e3bdcf 100644 --- a/src/blocks/blocks/advanced-heading/edit.js +++ b/src/blocks/blocks/advanced-heading/edit.js @@ -8,7 +8,7 @@ import hexToRgba from 'hex-rgba'; */ import { __ } from '@wordpress/i18n'; -import { isObjectLike, omitBy } from 'lodash'; +import { isObjectLike, isString, omitBy } from 'lodash'; import { createBlock, @@ -158,7 +158,7 @@ const Edit = ({ fontWeight: 'regular' === attributes.fontVariant ? 'normal' : attributes.fontVariant, fontStyle: attributes.fontStyle || undefined, textTransform: attributes.textTransform || undefined, - lineHeight: ( 3 < attributes.lineHeight ? attributes.lineHeight + 'px' : attributes.lineHeight ) || undefined, + lineHeight: ( ( ! isString( attributes.lineHeight ) && 3 < attributes.lineHeight ) ? attributes.lineHeight + 'px' : attributes.lineHeight ) || undefined, letterSpacing: _px( attributes.letterSpacing ), background: attributes.backgroundColor, ...textShadowStyle, diff --git a/src/blocks/blocks/advanced-heading/inspector.js b/src/blocks/blocks/advanced-heading/inspector.js index a7707864d..e07b0b8e1 100644 --- a/src/blocks/blocks/advanced-heading/inspector.js +++ b/src/blocks/blocks/advanced-heading/inspector.js @@ -34,6 +34,7 @@ import { } from '@wordpress/element'; import { + isEmpty, isObjectLike } from 'lodash'; @@ -55,6 +56,17 @@ import { useResponsiveAttributes } from '../../helpers/utility-hooks.js'; import { makeBox } from '../../plugins/copy-paste/utils'; import { _px } from '../../helpers/helper-functions.js'; import { useTabSwitch } from '../../helpers/block-utility'; +import TypographySelectorControl from '../../components/typography-selector-control'; + +const fieldMapping = { + 'fontFamily': 'fontFamily', + 'fontSize': 'fontSize', + 'lineHeight': 'lineHeight', + 'letterCase': 'textTransform', + 'spacing': 'letterSpacing', + 'appearance': 'fontStyle', + 'variant': 'fontVariant' +}; /** * @@ -204,97 +216,52 @@ const Inspector = ({ title={ __( 'Typography', 'otter-blocks' ) } initialOpen={ true } > - - - - responsiveSetAttributes( value, [ 'fontSize', 'fontSizeTablet', 'fontSizeMobile' ]) } - fontSizes={ - [ - { - name: __( '13', 'otter-blocks' ), - size: '13px', - slug: 'small' - }, - { - name: __( '20', 'otter-blocks' ), - size: '20px', - slug: 'medium' - }, - { - name: __( '36', 'otter-blocks' ), - size: '36px', - slug: 'large' - }, - { - name: __( '42', 'otter-blocks' ), - size: '42px', - slug: 'xl' - } - ] - } - /> - - - setAttributes({ fontVariant }) } - valueStyle={ attributes.fontStyle } - onChangeFontStyle={ fontStyle => setAttributes({ fontStyle }) } - valueTransform={ attributes.textTransform } - onChangeTextTransform={ textTransform => setAttributes({ textTransform }) } - /> - - setAttributes({ lineHeight }) } - step={ 0.1 } - min={ 0 } - units={[ - { - a11yLabel: 'Unitless (-)', - label: '-', - step: 0.1, - value: '' - }, - { - a11yLabel: 'Pixels (px)', - label: 'px', - step: 0.1, - value: 'px' - }, - { - a11yLabel: 'Percentage (%)', - label: '%', - step: 1, - value: '%' + { + setAttributes({ + fontFamily: values.fontFamily, + lineHeight: values.lineHeight, + fontStyle: values.appearance, + textTransform: values.letterCase, + letterSpacing: values.spacing, + [responsiveGetAttributes([ 'fontSize', 'fontSizeTablet', 'fontSizeMobile' ]) ?? 'fontSize']: values.fontSize, + fontVariant: values.variant + }); + } } + + onReset={ field => { + if ( 'fontSize' === field ) { + setAttributes({ + [responsiveGetAttributes([ 'fontSize', 'fontSizeTablet', 'fontSizeMobile' ])]: undefined + }); + } else { + setAttributes({ + [fieldMapping[field]]: undefined + }); } - ]} - /> - -
+ }} - setAttributes({ letterSpacing }) } - step={ 0.1 } - min={ -50 } - max={ 100 } + allowVariants={true} /> - - - { + useEffect( () => { const unsubscribe = blockInit( clientId, defaultAttributes ); return () => unsubscribe( attributes.id ); @@ -184,6 +191,16 @@ const Edit = ({ .map( taxonomy => select( 'core' ).getEntityRecords( 'taxonomy', taxonomy, { per_page: -1 }) ?? []) .flat(); + if ( window?.rankMathEditor ) { + + /** + * If RankMath is present on the page, we will refresh the RankMath editor analysis when the posts are updated. + */ + debounce( () => { + window?.rankMathEditor.refresh( 'content' ); + }, 500 ); + } + return { posts, categoriesList: categoriesList, @@ -329,4 +346,37 @@ const Edit = ({ ); }; +domReady( () => { + + /** + * If the RankMath plugin is present on the page, we will sent the content of the posts grid to RankMath for analysis. + */ + let maxTries = 10; + + const init = () => { + window.wp.hooks.addFilter( 'rank_math_content', 'rank-math', () => { + + /** + * @type {NodeListOf} postsHtml - The HTML nodes which contain the relevent post content for RankMath. + */ + const postsHtml = document.querySelectorAll( '.o-posts-grid-post-body' ); + return Array.from( postsHtml ).map( ( post ) => post.innerHTML ).join( '' ); + }); + + window?.rankMathEditor?.refresh( 'content' ); + }; + + const t = setInterval( () => { + if ( window?.rankMathEditor ) { + clearInterval( t ); + init(); + } else { + maxTries--; + if ( 0 === maxTries ) { + clearInterval( t ); + } + } + }, 1000 ); +}); + export default Edit; diff --git a/src/blocks/blocks/tabs/editor.scss b/src/blocks/blocks/tabs/editor.scss index d185aeb1d..568d1c90a 100644 --- a/src/blocks/blocks/tabs/editor.scss +++ b/src/blocks/blocks/tabs/editor.scss @@ -15,9 +15,12 @@ .wp-block-themeisle-blocks-tabs__header_item + div { margin-bottom: calc(0px - var(--border-side-width)); } - + .wp-block-themeisle-blocks-tabs__header_item { - + * { + cursor: text; + } + svg { fill: gray; max-width: 24px; @@ -39,7 +42,7 @@ margin-bottom: 0px; } - &> .block-editor-block-list__block { + &> .block-editor-block-list__block { border-color: inherit; &:not(:last-of-type) { &> .wp-block-themeisle-blocks-tabs-item__header { @@ -70,10 +73,10 @@ .wp-block-themeisle-blocks-tabs__content { > .block-editor-inner-blocks { height: 100%; - + > .block-editor-block-list__layout { height: 100%; - + > .block-editor-block-list__block { &:has( > .wp-block-themeisle-blocks-tabs-item__content.active) { @media (min-width: 800px) { @@ -84,7 +87,7 @@ } } } - } + } // &.is-style-boxed > .wp-block-themeisle-blocks-tabs__content { // &> .block-editor-inner-blocks { @@ -110,26 +113,26 @@ border-left-width: 0px; border-top-width: 0px; border-right-width: 0px; - + &:not(.active) { border-bottom-color: transparent; } - + &.active { border-bottom-style: solid; border-color: var(--active-title-border-color); } } - + &> .wp-block-themeisle-blocks-tabs-item__content { border-left-width: 0px; border-right-width: 0px; - + @media (max-width: 800px) { border-top-width: 0px; border-bottom-width: 0px; } - + @media (min-width: 800px) { border-bottom-width: 0px; } @@ -147,14 +150,14 @@ @media (max-width: 800px) { flex-direction: column; } - + &> .wp-block-themeisle-blocks-tabs-item__content { flex-grow: 1; } } } - + .add-header-container { display: flex; align-items: center; @@ -167,11 +170,11 @@ z-index: 10; } } - + &.has-pos-left { .add-header-container { height: 30px; - + &> .add-header-item > button { left: 0px; bottom: -10px; diff --git a/src/blocks/blocks/tabs/group/edit.js b/src/blocks/blocks/tabs/group/edit.js index 1287678ba..1d19ee791 100644 --- a/src/blocks/blocks/tabs/group/edit.js +++ b/src/blocks/blocks/tabs/group/edit.js @@ -12,12 +12,14 @@ import { createBlock } from '@wordpress/blocks'; import { InnerBlocks, + RichText, useBlockProps } from '@wordpress/block-editor'; import { useSelect, - useDispatch + useDispatch, + dispatch } from '@wordpress/data'; import { @@ -34,7 +36,7 @@ import metadata from './block.json'; import Inspector from './inspector.js'; import Controls from './controls.js'; import { blockInit, getDefaultValueByField } from '../../../helpers/block-utility.js'; -import { boxToCSS, objectOrNumberAsBox, _i, _px } from '../../../helpers/helper-functions'; +import { boxToCSS, objectOrNumberAsBox, _px } from '../../../helpers/helper-functions'; import classNames from 'classnames'; import BlockAppender from '../../../components/block-appender-button'; import { useDarkBackground } from '../../../helpers/utility-hooks.js'; @@ -45,7 +47,8 @@ const TabHeader = ({ tag, title, onClick, - active + active, + onChangeTitle }) => { const CustomTag = tag ?? 'div'; return ( @@ -58,7 +61,14 @@ const TabHeader = ({ ) } onClick={ onClick } > - { title } + ); }; @@ -251,9 +261,12 @@ const Edit = ({ toggleActiveTab( tabHeader.clientId ) } + onChangeTitle={ value => { + dispatch( 'core/block-editor' ).updateBlockAttributes( tabHeader.clientId, { title: value.replace( /(\r\n|\n|\r|
)/gm, '' ) }); + }} /> ); }) || '' } diff --git a/src/blocks/components/prompt/index.tsx b/src/blocks/components/prompt/index.tsx index a56059266..a16e31fb3 100644 --- a/src/blocks/components/prompt/index.tsx +++ b/src/blocks/components/prompt/index.tsx @@ -161,7 +161,7 @@ const PromptPlaceholder = ( props: PromptPlaceholderProps ) => { const [ generationStatus, setGenerationStatus ] = useState<'loading' | 'loaded' | 'error'>( 'loaded' ); - const [ apiKeyStatus, setApiKeyStatus ] = useState<'checking' | 'missing' | 'present' | 'error'>( 'checking' ); + const [ apiKeyStatus, setApiKeyStatus ] = useState<'checking' | 'missing' | 'present' | 'error'>( window.themeisleGutenberg?.hasOpenAiKey ? 'present' : 'checking' ); const [ embeddedPrompts, setEmbeddedPrompts ] = useState([]); const [ result, setResult ] = useState( undefined ); @@ -207,7 +207,7 @@ const PromptPlaceholder = ( props: PromptPlaceholderProps ) => { }, []); useEffect( () => { - if ( 'loading' === status ) { + if ( 'loading' === status || 'present' === apiKeyStatus ) { return; } @@ -218,7 +218,8 @@ const PromptPlaceholder = ( props: PromptPlaceholderProps ) => { } else { setApiKeyStatus( 'missing' ); } - if ( 'yes' === getOption( 'otter_blocks_logger_flag' ) ) { + + if ( window.themeisleGutenberg?.canTrack ) { setTrackingConsent( true ); setShowTrackingConsent( false ); } @@ -227,7 +228,7 @@ const PromptPlaceholder = ( props: PromptPlaceholderProps ) => { if ( 'error' === status ) { setApiKeyStatus( 'error' ); } - }, [ status, getOption ]); + }, [ status, getOption, apiKeyStatus ]); useEffect( () => { setResultHistoryIndex( resultHistory.length - 1 ); @@ -265,7 +266,7 @@ const PromptPlaceholder = ( props: PromptPlaceholderProps ) => { embeddedPrompt = injectActionIntoPrompt( embeddedPrompt, action ); } - if ( ! apiKey ) { + if ( 'present' !== apiKeyStatus ) { setShowError( true ); setErrorMessage( __( 'API Key not found. Please add your API Key in the settings page.', 'otter-blocks' ) ); return; diff --git a/src/blocks/components/typography-selector-control/editor.scss b/src/blocks/components/typography-selector-control/editor.scss index d56a05a9a..4010e349e 100644 --- a/src/blocks/components/typography-selector-control/editor.scss +++ b/src/blocks/components/typography-selector-control/editor.scss @@ -2,12 +2,12 @@ .o-typo-component { .o-typo-header { padding: 10px 0px 0px 0px; - + &> .components-heading { margin: 0; } } - + .o-two-column-components { gap: 10px; justify-content: space-between; @@ -16,10 +16,22 @@ justify-items: stretch; grid-template-columns: 1fr 1fr; max-height: 55px; - + + &> div:first-child:last-child { + grid-column: 1 / 3; + } + &> div > .components-base-control { margin: 8px 0px; } + + &~ .o-two-column-components { + margin-top: 10px; + } + } + + .components-font-size-picker__controls .components-base-control { + margin: 0 0 8px 0; } } @@ -28,11 +40,11 @@ } .block-editor-block-inspector, .o-options-global-defaults-modal { - .o-typo-component .components-base-control.o-no-margin { - margin: 0px; - + :is(.o-typo-component) :is(.components-base-control).o-no-margin { + margin: 0; + &> .components-base-control__field .components-base-control { - margin: 0px 0px 14px 0px; + margin: 0 0 14px 0; } } } diff --git a/src/blocks/components/typography-selector-control/index.tsx b/src/blocks/components/typography-selector-control/index.tsx index e4cb8b625..12761d3d8 100644 --- a/src/blocks/components/typography-selector-control/index.tsx +++ b/src/blocks/components/typography-selector-control/index.tsx @@ -30,6 +30,7 @@ import './editor.scss'; import { useInstanceId } from '@wordpress/compose'; import googleFontsLoader from '../../helpers/google-fonts'; import classNames from 'classnames'; +import { all } from 'deepmerge'; const TwoItemOnRow = ({ children }) => { return
@@ -45,6 +46,7 @@ interface IsEnabled { decoration: boolean; letterCase: boolean; lineHeight: boolean; + variant: boolean; } interface ComponentsValue { @@ -55,6 +57,7 @@ interface ComponentsValue { decoration: string; letterCase: string; lineHeight: string | number; + variant: string; } type OnChange = ( values: Partial ) => void @@ -66,7 +69,8 @@ const defaultStates = { spacing: false, decoration: false, letterCase: false, - lineHeight: false + lineHeight: false, + variant: false }, fontSizes: [ { @@ -112,8 +116,10 @@ const defaultStates = { spacing: __( 'Spacing', 'otter-blocks' ), decoration: __( 'Decoration', 'otter-blocks' ), letterCase: __( 'Letter Case', 'otter-blocks' ), - lineHeight: __( 'Line Height', 'otter-blocks' ) - } + lineHeight: __( 'Line Height', 'otter-blocks' ), + variant: __( 'Variant', 'otter-blocks' ) + }, + title: __( 'Font Size', 'otter-blocks' ) }; type TypographySelectorControlProps = { @@ -143,6 +149,11 @@ type TypographySelectorControlProps = { */ onChange?: OnChange + /** + * It triggers when the reset button is clicked. It offers the name of the component. + */ + onReset?: ( field: keyof ComponentsValue ) => void + /** * The font sizes options for FontSizePicker */ @@ -154,6 +165,16 @@ type TypographySelectorControlProps = { config?: { fontFamilyAsSelect?: boolean } + + /** + * The title of the component. + */ + title?: string + + /** + * Allow variants for the font family. + */ + allowVariants?: boolean } const TypographySelectorControl = ( props: TypographySelectorControlProps ) => { @@ -163,7 +184,10 @@ const TypographySelectorControl = ( props: TypographySelectorControlProps ) => { componentsValue, componentsDefaultValue, onChange, - showAsDisable + showAsDisable, + title, + onReset, + allowVariants } = props; const [ showComponent, setShowComponent ] = useState( @@ -174,12 +198,16 @@ const TypographySelectorControl = ( props: TypographySelectorControlProps ) => { onChange?.({ ...( componentsValue ?? {}), [ field ]: value }); }; + const onBulkChangeValue = ( values: Partial ) => { + onChange?.({ ...( componentsValue ?? {}), ...values }); + }; + const instanceId = useInstanceId( TypographySelectorControl ); const id = `inspector-google-fonts-control-${ instanceId }`; const [ fonts, setFonts ] = useState([]); - // const [ variants, setVariants ] = useState<{label: string, value: string}[]>([]); + const [ variants, setVariants ] = useState<{label: string, value: string}[]>([]); const [ search, setSearch ] = useState( '' ); const [ isLoading, setLoading ] = useState( false ); @@ -196,19 +224,21 @@ const TypographySelectorControl = ( props: TypographySelectorControlProps ) => { googleFontsLoader.afterLoading().then( ( loader ) => { setFonts( loader.fonts ); setLoading( false ); - - // if ( componentsValue?.fontFamily ) { - // setVariants( loader.getVariants( componentsValue?.fontFamily ) ); - // } }); } - }, [ showComponent?.fontFamily, fonts ]); + }, [ showComponent?.fontFamily, fonts, allowVariants, componentsValue?.fontFamily ]); + + useEffect( () => { + if ( allowVariants && componentsValue?.fontFamily ) { + setVariants( googleFontsLoader.getVariants( componentsValue?.fontFamily ) ); + } + }, [ allowVariants, componentsValue?.fontFamily ]); return (
-

{ __( 'Font Size', 'otter-blocks' ) }

+

{ title ?? defaultStates.title }

{ 'spacing', 'decoration', 'letterCase', - 'lineHeight' - ] as ( keyof IsEnabled )[]).map( ( component ) => { - if ( enableComponents?.[component]) { - return { - setShowComponent({ - ...showComponent, - [component]: ! Boolean( showComponent?.[component]) - }); - - onChange?.({ - [component]: undefined - }); - - } } - role="menuitemcheckbox" - > - { defaultStates.componentNames?.[component] } - ; - } - return ; - }) + 'lineHeight', + 'variant' + ] as ( keyof IsEnabled )[]) + .filter( o => { + return ! ( 'variant' === o && ! allowVariants ); + }).map( ( component ) => { + if ( enableComponents?.[component]) { + return { + setShowComponent({ + ...showComponent, + [component]: ! Boolean( showComponent?.[component]) + }); + + if ( showComponent?.[component]) { + onReset?.( component ); + } + } } + role="menuitemcheckbox" + > + { defaultStates.componentNames?.[component] } + ; + } + return ; + }) } @@ -281,7 +314,8 @@ const TypographySelectorControl = ( props: TypographySelectorControlProps ) => { spacing: undefined, decoration: undefined, letterCase: undefined, - lineHeight: undefined + lineHeight: undefined, + variant: undefined }); setShowComponent( defaultStates.isEnabled ); onClose?.(); @@ -309,6 +343,7 @@ const TypographySelectorControl = ( props: TypographySelectorControlProps ) => { /*@ts-ignore */ fontSizes={ props.fontSizes ?? defaultStates.fontSizes } onChange={ fontSize => onChangeValue( 'fontSize', fontSize?.toString() ) } + __nextHasNoMarginBottom={ true } /> @@ -367,9 +402,9 @@ const TypographySelectorControl = ( props: TypographySelectorControlProps ) => { setFonts( loader.fonts ); setLoading( false ); - // if ( componentsValue?.fontFamily ) { - // setVariants( loader.getVariants( componentsValue?.fontFamily ) ); - // } + if ( allowVariants && componentsValue?.fontFamily ) { + setVariants( loader.getVariants( componentsValue?.fontFamily ) ); + } }); } onToggle?.(); @@ -419,7 +454,11 @@ const TypographySelectorControl = ( props: TypographySelectorControlProps ) => { onClick={ () => { onToggle(); - onChangeValue( 'fontFamily', i.family ); + onBulkChangeValue({ + fontFamily: i.family, + variant: 'normal' + }); + setSearch( '' ); }} > @@ -456,6 +495,66 @@ const TypographySelectorControl = ( props: TypographySelectorControlProps ) => {
+ { + ( ( allowVariants && showComponent?.variant ) || showComponent?.lineHeight ) && ( + + { + allowVariants && showComponent?.variant && ( + + onChangeValue( 'variant', variant ) } + /> + + ) + } + { + showComponent?.lineHeight && ( + + onChangeValue( 'lineHeight', lineHeight ) } + units={[ + { + a11yLabel: 'Unitless (-)', + label: '-', + step: 0.1, + value: '' + }, + { + a11yLabel: 'Pixels (px)', + label: 'px', + step: 0.1, + value: 'px' + }, + { + a11yLabel: 'Percentage (%)', + label: '%', + step: 1, + value: '%' + } + ]} + /> + + ) + } + + ) + } + { ( showComponent?.appearance || showComponent?.spacing ) && ( @@ -591,25 +690,6 @@ const TypographySelectorControl = ( props: TypographySelectorControlProps ) => { ) } - - { - showComponent?.lineHeight && ( - - onChangeValue( 'lineHeight', lineHeight ) } - /> - - ) - } -
); }; diff --git a/src/blocks/global.d.ts b/src/blocks/global.d.ts index f1fc0f06b..ca4317afd 100644 --- a/src/blocks/global.d.ts +++ b/src/blocks/global.d.ts @@ -41,6 +41,7 @@ declare global { isAncestorTypeAvailable: boolean highlightDynamicText: boolean isPreview: boolean + hasOpenAiKey: boolean } otterPro?: Readonly<{ isActive: boolean diff --git a/src/blocks/helpers/use-settings.js b/src/blocks/helpers/use-settings.js index 5fec66137..85e89959e 100644 --- a/src/blocks/helpers/use-settings.js +++ b/src/blocks/helpers/use-settings.js @@ -71,8 +71,9 @@ const useSettings = () => { * @param {string?} success Success message for Notice. * @param {string?} noticeId Notice ID. * @param {function?} onSuccess Callback function to be executed on success. + * @param {function?} onError Callback function to be executed on error. */ - const updateOption = ( option, value, success = __( 'Settings saved.', 'otter-blocks' ), noticeId = undefined, onSuccess = () => {}) => { + const updateOption = ( option, value, success = __( 'Settings saved.', 'otter-blocks' ), noticeId = undefined, onSuccess = () => {}, onError = () => {}) => { setStatus( 'saving' ); const save = new api.models.Settings({ [option]: value }).save(); @@ -105,6 +106,7 @@ const useSettings = () => { } ); } + getSettings(); onSuccess?.( response ); }); @@ -114,13 +116,15 @@ const useSettings = () => { createNotice( 'error', - response.responseJSON.message ? response.responseJSON.message : __( 'An unknown error occurred.', 'otter-blocks' ), + response?.responseJSON?.message ?? __( 'An unknown error occurred.', 'otter-blocks' ), { isDismissible: true, type: 'snackbar', id: noticeId } ); + + onError?.( response ); }); }; diff --git a/src/blocks/test/e2e/blocks/animations.spec.js b/src/blocks/test/e2e/blocks/animations.spec.js new file mode 100644 index 000000000..69d570616 --- /dev/null +++ b/src/blocks/test/e2e/blocks/animations.spec.js @@ -0,0 +1,34 @@ +/** + * WordPress dependencies + */ +import { test, expect } from '@wordpress/e2e-test-utils-playwright'; + +test.describe( 'Animations', () => { + test.beforeEach( async({ admin }) => { + await admin.createNewPost(); + }); + + test( 'can add a typing animation"', async({ editor, page }) => { + await editor.insertBlock({ + name: 'core/paragraph', + attributes: { + content: 'Magna mollis sed ipsum convallis tellus donec. Maximus ligula nostra fusce inceptos in fermentum phasellus. Ante sollicitudin euismod ultrices nullam etiam eu. Himenaeos si ridiculus suscipit velit donec dui tristique. Habitant auctor ridiculus a consectetuer nisi volutpat magnis sed enim lacus. Quisque habitant litora sodales turpis montes.' + } + }); + + const box = await page.getByLabel( 'Paragraph block' ).boundingBox(); + + // Select a text inside the paragraph block. + await page.mouse.move( box.x + 10, box.y + 10 ); + await page.mouse.down(); + await page.mouse.move( box.x + box.width - 50, box.y + box.height - 100 ); + await page.mouse.up(); + + await page.getByLabel( 'More' ).click(); + + await page.getByRole( 'menuitem', { name: 'Typing Animation' }).click(); + + expect( page.getByLabel( 'Paragraph block' ).locator( 'o-anim-typing' ).first() ).toBeTruthy(); + expect( page.getByLabel( 'Paragraph block' ).locator( '.o-typing-delay-500ms' ).first() ).toBeTruthy(); + }); +}); diff --git a/src/blocks/test/e2e/blocks/heading.spec.js b/src/blocks/test/e2e/blocks/heading.spec.js new file mode 100644 index 000000000..bdf648d54 --- /dev/null +++ b/src/blocks/test/e2e/blocks/heading.spec.js @@ -0,0 +1,85 @@ +/** + * WordPress dependencies + */ +import { test, expect } from '@wordpress/e2e-test-utils-playwright'; + +test.describe( 'Advanced Heading Block', () => { + test.beforeEach( async({ admin }) => { + await admin.createNewPost(); + }); + + test( 'can be created by typing "/advanced-heading"', async({ editor, page }) => { + + // Create a Progress Block with the slash block shortcut. + await page.click( 'role=button[name="Add default block"i]' ); + await page.keyboard.type( '/advanced-heading' ); + await page.keyboard.press( 'Enter' ); + + const blocks = await editor.getBlocks(); + const hasProgressBar = blocks.some( ( block ) => 'themeisle-blocks/advanced-heading' === block.name ); + + expect( hasProgressBar ).toBeTruthy(); + }); + + test( 'can use typo component"', async({ editor, page }) => { + await editor.insertBlock({ + name: 'themeisle-blocks/advanced-heading' + }); + + // Open Style tab. + await page.getByRole( 'button', { name: 'Style' }).click(); + + // Select font size. + await page.getByRole( 'radio', { name: '16' }).click(); + + // Open the menu for more options. + await page.getByRole( 'button', { name: 'View options' }).click(); + + // Enable all options. + await page.getByRole( 'menuitemcheckbox', { name: 'Font Family' }).click(); + await page.getByRole( 'menuitemcheckbox', { name: 'Appearance' }).click(); + await page.getByRole( 'menuitemcheckbox', { name: 'Spacing' }).click(); + await page.getByRole( 'menuitemcheckbox', { name: 'Letter Case' }).click(); + await page.getByRole( 'menuitemcheckbox', { name: 'Line Height' }).click(); + await page.getByRole( 'menuitemcheckbox', { name: 'Variant' }).click(); + + // Close the menu. + await page.getByRole( 'button', { name: 'View options' }).click(); + + // Open the family font menu to start the fonts loading. Then close the panel and come back after some time. + await page.getByRole( 'button', { name: 'Font Family' }).click(); + + // Fill the line height. + await page.getByLabel( 'Line Height' ).fill( '1.5' ); + + // Fill the letter spacing. + await page.getByLabel( 'Spacing' ).fill( '10' ); + + // Select a letter case. + await page.getByRole( 'button', { name: 'Uppercase' }).click(); + + // Select an appearance. + await page.getByRole( 'combobox', { name: 'Appearance' }).selectOption( 'Italic' ); + + // Select a font family. + await page.getByRole( 'button', { name: 'Font Family' }).click(); + await page.waitForSelector( '.o-gfont-popover .components-menu-item__button div[style*="Comic Neue"]' ); + await page.getByRole( 'menuitem', { name: 'Comic Neue' }).click(); + + // Select a variant. + await page.getByRole( 'combobox', { name: 'Variants' }).selectOption( '700' ); + + // Compare with the saved data. + const blocks = await editor.getBlocks(); + const attrs = blocks.filter( ( block ) => 'themeisle-blocks/advanced-heading' === block.name )?.pop()?.attributes; + + expect( attrs ).toBeDefined(); + expect( attrs?.fontSize ).toBe( '16px' ); + expect( attrs?.fontFamily ).toBe( 'Comic Neue' ); + expect( attrs?.fontVariant ).toBe( '700' ); + expect( attrs?.letterSpacing ).toBe( '10px' ); + expect( attrs?.lineHeight ).toBe( '1.5' ); + expect( attrs?.textTransform ).toBe( 'uppercase' ); + expect( attrs?.fontStyle ).toBe( 'italic' ); + }); +}); diff --git a/src/blocks/test/e2e/blocks/tabs.spec.js b/src/blocks/test/e2e/blocks/tabs.spec.js index f9d370b0e..4b637f79a 100644 --- a/src/blocks/test/e2e/blocks/tabs.spec.js +++ b/src/blocks/test/e2e/blocks/tabs.spec.js @@ -68,4 +68,14 @@ test.describe( 'Tabs Block', () => { expect( await page.getByRole( 'paragraph' ).filter({ hasText: 'This is just a placeholder to help you visualize how the content is displayed in' }).isVisible() ).toBeTruthy(); }); + + test( 'change tab header content', async({ editor, page }) => { + await editor.insertBlock({ + name: 'themeisle-blocks/tabs' + }); + + await page.getByRole( 'textbox', { name: 'Add title…' }).first().fill( 'Tab 1000' ); + + await expect( page.locator( 'div' ).filter({ hasText: /^Tab 1000$/ }).first() ).toBeVisible(); + }); }); diff --git a/src/dashboard/components/Deal.js b/src/dashboard/components/Deal.js new file mode 100644 index 000000000..7dfe88bf8 --- /dev/null +++ b/src/dashboard/components/Deal.js @@ -0,0 +1,12 @@ +const Deal = ( props ) => { + return ( + + ); +}; + +export default Deal; diff --git a/src/dashboard/components/Main.js b/src/dashboard/components/Main.js index db7571ddc..a6879a633 100644 --- a/src/dashboard/components/Main.js +++ b/src/dashboard/components/Main.js @@ -24,6 +24,7 @@ import Integrations from './pages/Integrations.js'; import Feedback from './pages/Feedback.js'; import NoticeCard from './NoticeCard'; import { applyFilters } from '@wordpress/hooks'; +import Deal from './Deal'; let daysLeft = sprintf( __( '%s Days', 'otter-blocks' ), Number( window.otterObj.daysLeft ) ); @@ -77,6 +78,16 @@ const Main = ({ return ( + { + window.otterObj.deal.active && ( + + ) + }
{ 'dashboard' === currentTab && window.otterObj.showFeedbackNotice && ( diff --git a/src/dashboard/components/pages/Dashboard.js b/src/dashboard/components/pages/Dashboard.js index 8ca2d765d..1ea27e860 100644 --- a/src/dashboard/components/pages/Dashboard.js +++ b/src/dashboard/components/pages/Dashboard.js @@ -1,3 +1,8 @@ +/** + * External dependencies. + */ +import { isString } from 'lodash'; + /** * WordPress dependencies. */ @@ -18,6 +23,7 @@ import { dispatch } from '@wordpress/data'; import { Fragment, useEffect, + useReducer, useState } from '@wordpress/element'; @@ -25,23 +31,123 @@ import { * Internal dependencies. */ import ButtonControl from '../ButtonControl.js'; +import useSettings from '../../../blocks/helpers/use-settings'; + +const optionMapping = { + enableCustomCss: 'themeisle_blocks_settings_css_module', + enableBlocksAnimation: 'themeisle_blocks_settings_blocks_animation', + enableBlockConditions: 'themeisle_blocks_settings_block_conditions', + enableSectionDefaultBlock: 'themeisle_blocks_settings_default_block', + enableOptimizeAnimationsCss: 'themeisle_blocks_settings_optimize_animations_css', + enableRichSchema: 'themeisle_blocks_settings_disable_review_schema', + enableReviewScale: 'themeisle_blocks_settings_review_scale', + enableHighlightDynamic: 'themeisle_blocks_settings_highlight_dynamic', + enableAnonymousDataTracking: 'otter_blocks_logger_flag' +}; + +const initialState = { + values: { + enableCustomCss: false, + enableBlocksAnimation: false, + enableBlockConditions: false, + enableSectionDefaultBlock: false, + enableOptimizeAnimationsCss: false, + enableRichSchema: false, + enableReviewScale: false, + enableHighlightDynamic: false, + enableAnonymousDataTracking: 'no' + }, + status: { + enableCustomCss: 'init', + enableBlocksAnimation: 'init', + enableBlockConditions: 'init', + enableSectionDefaultBlock: 'init', + enableOptimizeAnimationsCss: 'init', + enableRichSchema: 'init', + enableReviewScale: 'init', + enableHighlightDynamic: 'init', + enableAnonymousDataTracking: 'init' + }, + dirty: { + enableCustomCss: false, + enableBlocksAnimation: false, + enableBlockConditions: false, + enableSectionDefaultBlock: false, + enableOptimizeAnimationsCss: false, + enableRichSchema: false, + enableReviewScale: false, + enableHighlightDynamic: false, + enableAnonymousDataTracking: false + }, + old: {} +}; + +/** + * Reducer. + * @param {Object} state The current state. + * @param {Object} action The action to be performed. + * @returns {*} + */ +const reducer = ( state, action ) => { + switch ( action.type ) { + case 'init': + state.values[ action.name ] = action.value; + state.status[ action.name ] = 'saved'; + return { ...state }; + + case 'update': + state.old[ action.name ] = isString( state.values[ action.name ]) ? state.values[ action.name ] : Boolean( state.values[ action.name ]); + state.values[ action.name ] = action.value; + state.dirty[ action.name ] = true; + return { ...state }; + + case 'status_bulk': + action.names.forEach( name => { + state.status[ name ] = action.value; + state.dirty[ name ] = false; + }); + return { ...state }; -const Dashboard = ({ - status, - getOption, - updateOption -}) => { + case 'saved': + state.status[ action.name ] = 'saved'; + state.values[ action.name ] = action.value; + state.old[ action.name ] = undefined; + return { ...state }; + + case 'rollback': + if ( undefined !== state.old[ action.name ]) { + state.values[action.name] = state.old[action.name]; + } + state.old[ action.name ] = undefined; + state.dirty[ action.name ] = false; + state.status[ action.name ] = 'saved'; + return { ...state }; + + default: + return state; + } +}; + +const Dashboard = () => { useEffect( () => { if ( ! Boolean( window.otterObj.stylesExist ) ) { setRegeneratedDisabled( true ); } }, []); + const [ getOption, updateOption, status ] = useSettings(); + const { createNotice } = dispatch( 'core/notices' ); const [ isRegeneratedDisabled, setRegeneratedDisabled ] = useState( false ); const [ isOpen, setOpen ] = useState( false ); + const [ state, applyAction ] = useReducer( reducer, initialState ); + + /** + * Regenerate styles. + * @returns {Promise} + */ const regenerateStyles = async() => { const data = await apiFetch({ path: 'otter/v1/regenerate', method: 'DELETE' }); @@ -58,6 +164,50 @@ const Dashboard = ({ setOpen( false ); }; + /** + * Initialize the state with values from the WordPress options. + */ + useEffect( () => { + if ( 'loaded' !== status ) { + return; + } + + Object.entries( state.status ) + .filter( ([ key, value ]) => 'init' === value ) + .forEach( ([ name, _ ]) => { + applyAction({ type: 'init', name, value: getOption( optionMapping[ name ]) }); + }); + }, [ state, status, getOption ]); + + /** + * Update the WordPress options. + */ + useEffect( () => { + const dirtyOptionNames = Object.entries( state.dirty ).filter( ([ key, value ]) => value ).map( ([ key, value ]) => key ); + + if ( dirtyOptionNames.length ) { + if ( 'error' !== status ) { + applyAction({ type: 'status_bulk', value: 'saving', names: dirtyOptionNames }); + } + + for ( const name of dirtyOptionNames ) { + updateOption( + optionMapping[ name ], + state.values[ name ], + __( 'Settings saved.', 'otter-blocks' ), + 'o-settings-saved-notice', + ( response ) => { + applyAction({ type: 'saved', name, value: response[ optionMapping[ name ] ] }); + }, + () => { + applyAction({ type: 'rollback', name }); + } + ); + } + } + + }, [ state, status ]); + return ( updateOption( 'themeisle_blocks_settings_css_module', ! Boolean( getOption( 'themeisle_blocks_settings_css_module' ) ) ) } + checked={ state.values.enableCustomCss } + disabled={ 'saving' === state.status.enableCustomCss } + onChange={ ( value ) => { + applyAction({ type: 'update', name: 'enableCustomCss', value }); + } } /> @@ -77,9 +229,9 @@ const Dashboard = ({ updateOption( 'themeisle_blocks_settings_blocks_animation', ! Boolean( getOption( 'themeisle_blocks_settings_blocks_animation' ) ) ) } + checked={ state.values.enableBlocksAnimation } + disabled={ 'saving' === state.status.enableBlocksAnimation } + onChange={ ( value ) => applyAction({ type: 'update', name: 'enableBlocksAnimation', value }) } /> @@ -87,9 +239,9 @@ const Dashboard = ({ updateOption( 'themeisle_blocks_settings_block_conditions', ! Boolean( getOption( 'themeisle_blocks_settings_block_conditions' ) ) ) } + checked={ state.values.enableBlockConditions } + disabled={ 'saving' === state.status.enableBlockConditions } + onChange={ ( value ) => applyAction({ type: 'update', name: 'enableBlockConditions', value }) } /> @@ -101,9 +253,9 @@ const Dashboard = ({ updateOption( 'themeisle_blocks_settings_default_block', ! Boolean( getOption( 'themeisle_blocks_settings_default_block' ) ) ) } + checked={ state.values.enableSectionDefaultBlock } + disabled={ 'saving' === state.status.enableSectionDefaultBlock } + onChange={ ( value ) => applyAction({ type: 'update', name: 'enableSectionDefaultBlock', value }) } /> @@ -111,9 +263,9 @@ const Dashboard = ({ updateOption( 'themeisle_blocks_settings_optimize_animations_css', ! Boolean( getOption( 'themeisle_blocks_settings_optimize_animations_css' ) ) ) } + checked={ state.values.enableOptimizeAnimationsCss } + disabled={ 'saving' === state.status.enableOptimizeAnimationsCss } + onChange={ ( value ) => applyAction({ type: 'update', name: 'enableOptimizeAnimationsCss', value }) } /> @@ -121,9 +273,9 @@ const Dashboard = ({ updateOption( 'themeisle_blocks_settings_disable_review_schema', ! Boolean( getOption( 'themeisle_blocks_settings_disable_review_schema' ) ) ) } + checked={ state.values.enableRichSchema } + disabled={ 'saving' === state.status.enableRichSchema } + onChange={ ( value ) => applyAction({ type: 'update', name: 'enableRichSchema', value }) } /> @@ -131,9 +283,9 @@ const Dashboard = ({ updateOption( 'themeisle_blocks_settings_review_scale', ! Boolean( getOption( 'themeisle_blocks_settings_review_scale' ) ) ) } + checked={ state.values.enableReviewScale } + disabled={ 'saving' === state.status.enableReviewScale } + onChange={ ( value ) => applyAction({ type: 'update', name: 'enableReviewScale', value }) } /> @@ -141,9 +293,9 @@ const Dashboard = ({ updateOption( 'themeisle_blocks_settings_highlight_dynamic', ! Boolean( getOption( 'themeisle_blocks_settings_highlight_dynamic' ) ) ) } + checked={ state.values.enableHighlightDynamic } + disabled={ 'saving' === state.status.enableHighlightDynamic } + onChange={ ( value ) => applyAction({ type: 'update', name: 'enableHighlightDynamic', value }) } /> @@ -151,9 +303,9 @@ const Dashboard = ({ updateOption( 'otter_blocks_logger_flag', ( 'yes' === getOption( 'otter_blocks_logger_flag' ) ? 'no' : 'yes' ) ) } + checked={ 'yes' === state.values.enableAnonymousDataTracking } + disabled={ 'saving' === state.status.enableAnonymousDataTracking } + onChange={ ( value ) => applyAction({ type: 'update', name: 'enableAnonymousDataTracking', value: value ? 'yes' : 'no' }) } /> diff --git a/src/dashboard/style.scss b/src/dashboard/style.scss index 9430a1563..f28e8b52c 100644 --- a/src/dashboard/style.scss +++ b/src/dashboard/style.scss @@ -27,12 +27,18 @@ } #otter { + --main-link-color: #2271b1; + .components-button { &.is-primary { --wp-admin-theme-color: #ed6f57; --wp-admin-theme-color-darker-20: #d5654f; --wp-admin-theme-color-darker-10: #dd6851; } + + &.is-link { + color: var(--main-link-color); + } } .otter-header { @@ -89,9 +95,17 @@ margin: 0; border: 0; cursor: pointer; + margin-right: 8px; + + &:hover { + span { + color: #282828; + } + border-bottom: 3px solid #e5e5e5; + } &.is-active { - border-bottom: 4px solid #1E7DB2; + border-bottom: 3px solid #1E7DB2; span { color: #282828; @@ -100,7 +114,7 @@ @media ( max-width: 960px ) { border-bottom: 0; - border-left: 4px solid #1E7DB2; + border-left: 3px solid #1E7DB2; } } @@ -161,10 +175,17 @@ &:hover { border-bottom: 1px solid #d6e2ed !important; + background: #E8E8E8; } } } + .components-panel__body-toggle { + &:hover { + background: #E8E8E8; + } + } + &.is-pro { .components-panel__body-title { .components-button { @@ -247,8 +268,8 @@ } &:focus { - border-color: #00a0d2; - box-shadow: 0 0 0 1px #00a0d2; + border-color: var(--main-link-color); + box-shadow: 0 0 0 1px var(--main-link-color); outline: 2px solid transparent; outline-offset: -2px; } @@ -290,6 +311,12 @@ padding: 2px 20px; font-size: 14px; margin-right: 10px; + color: var(--main-link-color); + box-shadow: inset 0 0 0 1px var(--main-link-color); + + &:focus { + box-shadow: inset 0 0 0 1.5px var(--main-link-color); + } } .components-external-link { @@ -342,10 +369,16 @@ .is-secondary { padding: 2px 20px; font-size: 14px; + color: var(--main-link-color); + box-shadow: inset 0 0 0 1px var(--main-link-color); &:not(:last-child) { margin-right: 15px; } + + &:focus { + box-shadow: inset 0 0 0 1.5px var(--main-link-color); + } } } } @@ -484,6 +517,24 @@ justify-content: center; } } + + .components-form-toggle.is-checked .components-form-toggle__track { + background-color: var(--main-link-color); + } + + #o-feedback { + box-shadow: inset 0 0 0 1px var(--main-link-color); + color: var(--main-link-color); + + &:focus { + box-shadow: inset 0 0 0 1.5px var(--main-link-color); + } + } + + :is(.components-textarea-control__input, .components-text-control__input):focus { + border-color: var(--main-link-color); + box-shadow: 0 0 0 1px var(--main-link-color); + } } .otter-button-control { @@ -529,3 +580,48 @@ padding-left: 16px; padding-right: 16px; } + +.otter-deal { + display: flex; + align-items: center; + justify-content: center; + + margin-top: 20px; + + a { + position: relative; + } + + img { + width: 100%; + } + + .o-urgency { + position: absolute; + + top: 10%; + left: 2.1%; + + color: #FFF; + font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 14px; + font-style: normal; + font-weight: 700; + line-height: normal; + letter-spacing: 0.3px; + text-transform: uppercase; + } +} + +@media(max-width: 480px) { + .otter-deal .o-urgency { + font-size: 7px; + } +} + +@media (min-width: 481px) and (max-width: 1024px) { + .otter-deal .o-urgency { + font-size: 10px; + } +} + diff --git a/webpack.config.js b/webpack.config.js index 403006677..9eb235ce7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -162,11 +162,25 @@ module.exports = [ events: { onEnd: { mkdir: blockFolders, - copy: blockFiles + copy: blockFiles, + delete: [ + 'build/animation/blocks/', + 'build/animation/pro/', + 'build/css/blocks/', + 'build/css/pro/', + 'build/dashboard/blocks/', + 'build/dashboard/pro/', + 'build/export-import/blocks/', + 'build/export-import/pro/', + 'build/blocks/pro/', + 'build/blocks/blocks/', + 'build/pro/pro', + 'build/pro/blocks' + ] } }, runOnceInWatchMode: false, - runTasksInSeries: true + runTasksInSeries: false }), new BundleAnalyzerPlugin({ analyzerMode: 'disabled',