diff --git a/.eslintrc b/.eslintrc index 2be0158ad..23842686c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -45,6 +45,8 @@ "@typescript-eslint/prefer-nullish-coalescing": ["error"], // Enabling TS rule and disabling Base rule as it can report incorrect errors. "no-shadow": "off", - "@typescript-eslint/no-shadow": ["error"] + "@typescript-eslint/no-shadow": ["error"], + // Allow usage of Gutenberg experimental components. + "@wordpress/no-unsafe-wp-apis": "off" } } diff --git a/.github/workflows/cs-lint.yml b/.github/workflows/cs-lint.yml index f94053ddf..5312d2fac 100644 --- a/.github/workflows/cs-lint.yml +++ b/.github/workflows/cs-lint.yml @@ -53,7 +53,7 @@ jobs: # Install dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-composer-dependencies - name: Install Composer dependencies - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 # Lint PHP. - name: Lint PHP against parse errors diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index b9e3d1755..1202e6e67 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v4 - name: Use desired version of NodeJS - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version: 16 cache: npm diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 3da1c39a0..e40393ded 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -73,11 +73,11 @@ jobs: - name: Install Composer dependencies if: ${{ matrix.php < 8.2 }} - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 - name: Install Composer dependencies for PHP >= 8.2 if: ${{ matrix.php >= 8.2 }} - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 with: composer-options: --ignore-platform-reqs diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 0b7631e8e..8b0bfe79b 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version: ${{ matrix.node-version }} cache: npm diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 08fdf74c0..e5b8b79f3 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -66,11 +66,11 @@ jobs: - name: Install Composer dependencies for PHP < 8.2 if: ${{ matrix.php < 8.2 }} - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 - name: Install Composer dependencies for PHP >= 8.2 if: ${{ matrix.php >= 8.2 }} - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 with: composer-options: --ignore-platform-reqs diff --git a/.husky/pre-commit b/.husky/pre-commit index 52a445c12..728606848 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,6 +1,3 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - npm run lint composer cs vendor/bin/phpstan analyse --memory-limit=1G diff --git a/.wordpress-org/screenshot-10.png b/.wordpress-org/screenshot-10.png deleted file mode 100644 index d0e3bc9a9..000000000 Binary files a/.wordpress-org/screenshot-10.png and /dev/null differ diff --git a/.wordpress-org/screenshot-11.png b/.wordpress-org/screenshot-11.png deleted file mode 100644 index 5188a0629..000000000 Binary files a/.wordpress-org/screenshot-11.png and /dev/null differ diff --git a/.wordpress-org/screenshot-3.png b/.wordpress-org/screenshot-3.png index bcf4e7877..3bf31c923 100644 Binary files a/.wordpress-org/screenshot-3.png and b/.wordpress-org/screenshot-3.png differ diff --git a/.wordpress-org/screenshot-4.png b/.wordpress-org/screenshot-4.png index 0b6d9bbbc..af45edb1e 100644 Binary files a/.wordpress-org/screenshot-4.png and b/.wordpress-org/screenshot-4.png differ diff --git a/.wordpress-org/screenshot-5.png b/.wordpress-org/screenshot-5.png index 4295a5e77..ab96171af 100644 Binary files a/.wordpress-org/screenshot-5.png and b/.wordpress-org/screenshot-5.png differ diff --git a/.wordpress-org/screenshot-6.png b/.wordpress-org/screenshot-6.png index 5cfbf10f6..03c370658 100644 Binary files a/.wordpress-org/screenshot-6.png and b/.wordpress-org/screenshot-6.png differ diff --git a/.wordpress-org/screenshot-7.png b/.wordpress-org/screenshot-7.png index e880c6873..f43dd013b 100644 Binary files a/.wordpress-org/screenshot-7.png and b/.wordpress-org/screenshot-7.png differ diff --git a/.wordpress-org/screenshot-8.png b/.wordpress-org/screenshot-8.png index 3f9db3f85..81c0aed7d 100644 Binary files a/.wordpress-org/screenshot-8.png and b/.wordpress-org/screenshot-8.png differ diff --git a/.wordpress-org/screenshot-9.png b/.wordpress-org/screenshot-9.png index 6ec4857c6..5188a0629 100644 Binary files a/.wordpress-org/screenshot-9.png and b/.wordpress-org/screenshot-9.png differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 69dfa92d9..620d2d614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.14.0](https://github.com/Parsely/wp-parsely/compare/3.13.3...3.14.0) - 2024-03-12 + +### Added + +- Add option for full metadata in non-posts ([#2250](https://github.com/Parsely/wp-parsely/pull/2250)) +- PCH: Add Smart Linking feature ([#2116](https://github.com/Parsely/wp-parsely/pull/2116)) + +### Changed + +- PCH: Redesign the PCH Editor Sidebar ([#2238](https://github.com/Parsely/wp-parsely/pull/2238)) +- PCH: Update Content Suggestions API URL to the new version ([#2223](https://github.com/Parsely/wp-parsely/pull/2223)) +- Refactor endpoints availability code ([#2198](https://github.com/Parsely/wp-parsely/pull/2198)) +- PCH Settings: Refactor the client-side Settings API ([#2193](https://github.com/Parsely/wp-parsely/pull/2193)) + +### Dependency Updates + +- The list of all dependency updates for this release is available [here](https://github.com/Parsely/wp-parsely/pulls?q=is%3Apr+is%3Amerged+milestone%3A3.14.0+label%3A%22Component%3A+Dependencies%22). + ## [3.13.3](https://github.com/Parsely/wp-parsely/compare/3.13.2...3.13.3) - 2024-02-01 ### Fixed diff --git a/README.md b/README.md index fdf723476..341fc93f5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Parse.ly -Stable tag: 3.13.3 +Stable tag: 3.14.0 Requires at least: 5.2 Tested up to: 6.4 Requires PHP: 7.2 @@ -40,9 +40,10 @@ The [Content Helper](https://docs.parse.ly/plugin-content-helper/) is a set of c - The [Parse.ly Dashboard Widget](https://docs.parse.ly/plugin-content-helper/#h-dashboard) - Displays the site's top posts in the WordPress Dashboard. - The [Parse.ly Stats Column](https://docs.parse.ly/plugin-content-helper/#h-posts) - Displays published post performance for the last 7 days in Post Lists. - The [Parse.ly Editor Sidebar](https://docs.parse.ly/plugin-content-helper/#h-editor) - This sidebar is integrated into the WordPress Editor and offers insights about the content currently being edited such as: - - [Performance Details](https://docs.parse.ly/plugin-content-helper/#h-performance-details) - Displays performance metrics about the content currently being edited. - - [Related Top Posts](https://docs.parse.ly/plugin-content-helper/#h-related-top-posts) - Displays a list of the website’s most successful posts, similar to the post/page currently being edited. - [Title Suggestions](https://docs.parse.ly/plugin-content-helper/#h-title-suggestions-beta) - Generates title suggestions for the post/page currently being edited. + - [Smart Linking](https://docs.parse.ly/plugin-content-helper/#h-smart-linking-beta) - Automatically adds links to the current content that point to the most relevant and top performing posts. + - [Related Posts](https://docs.parse.ly/plugin-content-helper/#h-related-top-posts) - Displays a list of the website’s most successful posts, similar to the post/page currently being edited. + - [Performance Stats](https://docs.parse.ly/plugin-content-helper/#h-performance-details) - Displays performance metrics about the content currently being edited. - The [Parse.ly Excerpt Generator](https://docs.parse.ly/plugin-content-helper/#h-excerpt-generator-beta) - A Post Editor settings enhancement that generates an excerpt for the post/page currently being edited. #### The Parse.ly Recommendations Block @@ -115,21 +116,17 @@ Please visit the [changelog](https://github.com/parsely/wp-parsely/blob/trunk/CH ![Parse.ly Dashboard Widget](.wordpress-org/screenshot-1.png) 2. The Parse.ly Stats Column (on the right), showing information about content that is being tracked as Posts. ![Parse.ly List Column](.wordpress-org/screenshot-2.png) -3. The Parse.ly Editor Sidebar, featuring the Performance Details panel. - ![Parse.ly Editor Sidebar - Performance Details](.wordpress-org/screenshot-3.png) -4. The Parse.ly Editor Sidebar, featuring the Related Top Posts panel. - ![Parse.ly Editor Sidebar - Related Top Posts](.wordpress-org/screenshot-4.png) -5. The Parse.ly Editor Sidebar, featuring the Title Suggestions panel. - ![Parse.ly Editor Sidebar - Title Suggestions](.wordpress-org/screenshot-5.png) -6. The Parse.ly Excerpt Generator in the Post Editor's settings. - ![Parse.ly Excerpt Generator](.wordpress-org/screenshot-6.png) -7. The Recommendations Block, showcasing links to related content on your site. - ![Parse.ly Recommendations Block](.wordpress-org/screenshot-7.png) -8. Parse.ly plugin basic settings for easy setup. For the plugin to start working, only the Site ID is needed. - ![Parse.ly Plugin - Basic Settings](.wordpress-org/screenshot-8.png) -9. Parse.ly plugin settings that require a website recrawl whenever they are updated. - ![Parse.ly Plugin - Recrawl Settings](.wordpress-org/screenshot-9.png) -10. Parse.ly plugin advanced settings, to be used only if instructed by Parse.ly staff. - ![Parse.ly Plugin - Advanced Settings](.wordpress-org/screenshot-10.png) -11. A view of the Parse.ly Dashboard Overview. Parse.ly offers analytics that empower you to better understand how your content is performing. - ![Parse.ly Dashboard Overview](.wordpress-org/screenshot-11.png) +3. The Parse.ly Editor Sidebar, featuring the Title Suggestions panel. + ![Parse.ly Editor Sidebar - Title Suggestions](.wordpress-org/screenshot-3.png) +4. The Parse.ly Editor Sidebar, featuring the Smart Linking panel. + ![Parse.ly Editor Sidebar - Smart Linking](.wordpress-org/screenshot-4.png) +5. The Parse.ly Editor Sidebar, featuring the Related Posts panel. + ![Parse.ly Editor Sidebar - Related Posts](.wordpress-org/screenshot-5.png) +6. The Parse.ly Editor Sidebar, featuring the Performance Stats panel. + ![Parse.ly Editor Sidebar - Performance Stats](.wordpress-org/screenshot-6.png) +7. The Parse.ly Excerpt Generator in the Post Editor's settings. + ![Parse.ly Excerpt Generator](.wordpress-org/screenshot-7.png) +8. The Recommendations Block, showcasing links to related content on your site. + ![Parse.ly Recommendations Block](.wordpress-org/screenshot-8.png) +9. A view of the Parse.ly Dashboard Overview. Parse.ly offers analytics that empower you to better understand how your content is performing. + ![Parse.ly Dashboard Overview](.wordpress-org/screenshot-9.png) diff --git a/build/admin-settings.asset.php b/build/admin-settings.asset.php index b5514bb69..ce69630c1 100644 --- a/build/admin-settings.asset.php +++ b/build/admin-settings.asset.php @@ -1 +1 @@ - array(), 'version' => '769e007a999eeb6e2141'); + array(), 'version' => '36f1fd1436d4fef05e1e'); diff --git a/build/admin-settings.css b/build/admin-settings.css index 881bfa9db..ec02addeb 100644 --- a/build/admin-settings.css +++ b/build/admin-settings.css @@ -1 +1 @@ -#wp-parsely-dashboard-widget,.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e}.settings_page_parsely{--padding-default:15px}.settings_page_parsely #track-post-types{max-width:550px;width:100%}.settings_page_parsely #track-post-types td,.settings_page_parsely #track-post-types th{display:table-cell;padding:var(--padding-default);text-align:center;vertical-align:middle}.settings_page_parsely #track-post-types td{padding:0}.settings_page_parsely #track-post-types td label{display:inline-block;margin:0!important;padding-bottom:var(--padding-default);padding-top:var(--padding-default);width:100%}.settings_page_parsely #track-post-types thead th:nth-child(3){word-break:break-word}.settings_page_parsely fieldset:disabled *,.settings_page_parsely tr:has(fieldset:disabled) *{color:var(--gray-500);cursor:default}.settings_page_parsely .managed-option-badge{border:.0625rem solid var(--green-500);border-radius:.25rem;color:var(--green-500)!important;display:inline-block;font-size:.625rem;padding:.125rem .25rem;text-decoration:none}.settings_page_parsely a.managed-option-badge{cursor:pointer!important}.settings_page_parsely a.managed-option-badge:hover{background-color:var(--green-500);color:#fff!important}@media only screen and (max-width:380px){.settings_page_parsely #track-post-types td,.settings_page_parsely #track-post-types th{padding-left:10px;padding-right:10px}.settings_page_parsely #track-post-types th:first-child{max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}} +#wp-parsely-dashboard-widget,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"],.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-panel,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--parsely-green-10:#c7ecb1;--parsely-green-65:#2a691b;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--grid-unit-5:0.25rem;--grid-unit-10:0.5rem;--grid-unit-15:0.75rem;--grid-unit-20:1rem;--font-size--smaller:0.688rem;--font-size--small:0.75rem;--font-size--medium:0.875rem;--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e;--sidebar-white:#f0f0f0}.settings_page_parsely{--padding-default:15px}.settings_page_parsely #track-post-types{max-width:550px;width:100%}.settings_page_parsely #track-post-types td,.settings_page_parsely #track-post-types th{display:table-cell;padding:var(--padding-default);text-align:center;vertical-align:middle}.settings_page_parsely #track-post-types td{padding:0}.settings_page_parsely #track-post-types td label{display:inline-block;margin:0!important;padding-bottom:var(--padding-default);padding-top:var(--padding-default);width:100%}.settings_page_parsely #track-post-types thead th:nth-child(3){word-break:break-word}.settings_page_parsely fieldset:disabled *,.settings_page_parsely tr:has(fieldset:disabled) *{color:var(--gray-500);cursor:default}.settings_page_parsely .managed-option-badge{border:.0625rem solid var(--green-500);border-radius:.25rem;color:var(--green-500)!important;display:inline-block;font-size:.625rem;padding:.125rem .25rem;text-decoration:none}.settings_page_parsely a.managed-option-badge{cursor:pointer!important}.settings_page_parsely a.managed-option-badge:hover{background-color:var(--green-500);color:#fff!important}@media only screen and (max-width:380px){.settings_page_parsely #track-post-types td,.settings_page_parsely #track-post-types th{padding-left:10px;padding-right:10px}.settings_page_parsely #track-post-types th:first-child{max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}} diff --git a/build/blocks/recommendations/edit.asset.php b/build/blocks/recommendations/edit.asset.php index 11b5a05bb..d07e3e227 100644 --- a/build/blocks/recommendations/edit.asset.php +++ b/build/blocks/recommendations/edit.asset.php @@ -1 +1 @@ - array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '44ef23fe00e4fd1314e1'); + array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => 'ada8db34ea07d80ab473'); diff --git a/build/blocks/recommendations/edit.js b/build/blocks/recommendations/edit.js index 9480dc879..ac88aae08 100644 --- a/build/blocks/recommendations/edit.js +++ b/build/blocks/recommendations/edit.js @@ -1 +1 @@ -!function(){"use strict";var e,n={204:function(e,n,r){var t,o,a=r(893),i=window.wp.blockEditor,l=window.wp.blocks,s=window.wp.i18n,c=window.wp.components,u=JSON.parse('{"u2":"wp-parsely/recommendations","Y4":{"imagestyle":{"type":"string","default":"original"},"limit":{"type":"number","default":3},"openlinksinnewtab":{"type":"boolean","default":false},"showimages":{"type":"boolean","default":true},"sort":{"type":"string","default":"score"},"title":{"type":"string","default":"Related Content"}}}'),d=window.wp.element;(o=t||(t={}))[o.Error=0]="Error",o[o.Loaded=1]="Loaded",o[o.Recommendations=2]="Recommendations";var p=function(){return p=Object.assign||function(e){for(var n,r=1,t=arguments.length;r0&&o[o.length-1])||6!==l[0]&&2!==l[0])){i=0;continue}if(3===l[0]&&(!o||l[1]>o[0]&&l[1]=a)&&Object.keys(t.O).every((function(e){return t.O[e](r[s])}))?r.splice(s--,1):(l=!1,a0&&e[u-1][2]>a;u--)e[u]=e[u-1];e[u]=[r,o,a]},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,{a:n}),n},t.d=function(e,n){for(var r in n)t.o(n,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:n[r]})},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},function(){var e={878:0,570:0};t.O.j=function(n){return 0===e[n]};var n=function(n,r){var o,a,i=r[0],l=r[1],s=r[2],c=0;if(i.some((function(n){return 0!==e[n]}))){for(o in l)t.o(l,o)&&(t.m[o]=l[o]);if(s)var u=s(t)}for(n&&n(r);c0&&o[o.length-1])||6!==l[0]&&2!==l[0])){i=0;continue}if(3===l[0]&&(!o||l[1]>o[0]&&l[1]=a)&&Object.keys(t.O).every((function(e){return t.O[e](r[s])}))?r.splice(s--,1):(l=!1,a0&&e[u-1][2]>a;u--)e[u]=e[u-1];e[u]=[r,o,a]},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,{a:n}),n},t.d=function(e,n){for(var r in n)t.o(n,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:n[r]})},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},function(){var e={878:0,570:0};t.O.j=function(n){return 0===e[n]};var n=function(n,r){var o,a,i=r[0],l=r[1],s=r[2],c=0;if(i.some((function(n){return 0!==e[n]}))){for(o in l)t.o(l,o)&&(t.m[o]=l[o]);if(s)var u=s(t)}for(n&&n(r);c array('react', 'wp-api-fetch', 'wp-components', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '4a49c9901d461440c000'); + array('react', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '68d9813064fecfe775a1'); diff --git a/build/content-helper/dashboard-widget.css b/build/content-helper/dashboard-widget.css index f58d9b642..18586ed33 100644 --- a/build/content-helper/dashboard-widget.css +++ b/build/content-helper/dashboard-widget.css @@ -1 +1 @@ -#wp-parsely-dashboard-widget,.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e}#wp-parsely-dashboard-widget{color:var(--base-text);font-family:var(--base-font)}#wp-parsely-dashboard-widget .parsely-spinner-wrapper{display:flex;justify-content:center;margin:11.875rem 0}#wp-parsely-dashboard-widget .parsely-spinner-wrapper svg{height:22px;width:22px}#wp-parsely-dashboard-widget .content-helper-error-message{margin-top:1.125rem}#wp-parsely-dashboard-widget p.content-helper-error-message-hint{color:var(--gray-700)}#wp-parsely-dashboard-widget .parsely-top-posts-filters{display:flex;justify-content:space-between;margin-bottom:.625rem}#wp-parsely-dashboard-widget .parsely-top-posts-navigation{display:flex;justify-content:space-between;margin-top:.625rem}#wp-parsely-dashboard-widget .parsely-top-posts-navigation button{background:none;border:none;color:var(--blue-550);font-size:.75rem;font-weight:600;text-decoration:none}#wp-parsely-dashboard-widget .parsely-top-posts-navigation button:disabled{visibility:hidden}#wp-parsely-dashboard-widget .parsely-top-posts-navigation button:not([disabled]):hover{cursor:pointer}#wp-parsely-dashboard-widget .parsely-top-post-content{display:flex}#wp-parsely-dashboard-widget .parsely-top-post-content:before{content:counter(item) "";counter-increment:item;padding-right:.5rem}@media only screen and (max-width:380px){#wp-parsely-dashboard-widget .parsely-top-post-content:before{content:"";padding-right:0}}#wp-parsely-dashboard-widget .parsely-top-posts{list-style:none;margin:1rem 0 0}#wp-parsely-dashboard-widget .parsely-top-post{margin-bottom:1rem}#wp-parsely-dashboard-widget .parsely-top-post-thumbnail{height:46px;min-width:46px}#wp-parsely-dashboard-widget .parsely-top-post-thumbnail img{height:100%;width:100%}#wp-parsely-dashboard-widget .parsely-top-post-data{border-top:1px solid var(--gray-300);flex-grow:1;margin-left:.5rem;padding-top:.25rem}#wp-parsely-dashboard-widget .parsely-top-post-title{color:var(--base-text);font-size:.875rem;margin-right:.4375rem}#wp-parsely-dashboard-widget a.parsely-top-post-title:hover{color:var(--blue-550)}#wp-parsely-dashboard-widget .parsely-top-post-icon-link{position:relative;top:.25rem}#wp-parsely-dashboard-widget .parsely-top-post-icon-link svg{fill:#8d98a1;margin-right:.1875rem}#wp-parsely-dashboard-widget .parsely-top-post-icon-link svg:hover{fill:var(--blue-550)}#wp-parsely-dashboard-widget .parsely-top-post-metadata{margin:.25rem 0 0}#wp-parsely-dashboard-widget .parsely-top-post-metadata>span{color:var(--gray-500)}#wp-parsely-dashboard-widget .parsely-top-post-metadata>span:not(:first-child){margin-left:.75rem}#wp-parsely-dashboard-widget .parsely-top-post-metric-data{float:right;font-family:var(--numeric-font);font-size:1.125rem;padding-left:.625rem} +#wp-parsely-dashboard-widget,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"],.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-panel,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--parsely-green-10:#c7ecb1;--parsely-green-65:#2a691b;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--grid-unit-5:0.25rem;--grid-unit-10:0.5rem;--grid-unit-15:0.75rem;--grid-unit-20:1rem;--font-size--smaller:0.688rem;--font-size--small:0.75rem;--font-size--medium:0.875rem;--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e;--sidebar-white:#f0f0f0}#wp-parsely-dashboard-widget{color:var(--base-text);font-family:var(--base-font)}#wp-parsely-dashboard-widget .parsely-spinner-wrapper{display:flex;justify-content:center;margin:11.875rem 0}#wp-parsely-dashboard-widget .parsely-spinner-wrapper svg{height:22px;width:22px}#wp-parsely-dashboard-widget .content-helper-error-message{margin-top:1.125rem}#wp-parsely-dashboard-widget p.content-helper-error-message-hint{color:var(--gray-700)}#wp-parsely-dashboard-widget .parsely-top-posts-filters{display:flex;justify-content:space-between;margin-bottom:.625rem}#wp-parsely-dashboard-widget .parsely-top-posts-navigation{display:flex;justify-content:space-between;margin-top:.625rem}#wp-parsely-dashboard-widget .parsely-top-posts-navigation button{background:none;border:none;color:var(--blue-550);font-size:.75rem;font-weight:600;text-decoration:none}#wp-parsely-dashboard-widget .parsely-top-posts-navigation button:disabled{visibility:hidden}#wp-parsely-dashboard-widget .parsely-top-posts-navigation button:not([disabled]):hover{cursor:pointer}#wp-parsely-dashboard-widget .parsely-top-post-content{display:flex}#wp-parsely-dashboard-widget .parsely-top-post-content:before{content:counter(item) "";counter-increment:item;padding-right:.5rem}@media only screen and (max-width:380px){#wp-parsely-dashboard-widget .parsely-top-post-content:before{content:"";padding-right:0}}#wp-parsely-dashboard-widget .parsely-top-posts{list-style:none;margin:1rem 0 0}#wp-parsely-dashboard-widget .parsely-top-post{margin-bottom:1rem}#wp-parsely-dashboard-widget .parsely-top-post-thumbnail{height:46px;min-width:46px}#wp-parsely-dashboard-widget .parsely-top-post-thumbnail img{height:100%;width:100%}#wp-parsely-dashboard-widget .parsely-top-post-data{border-top:1px solid var(--gray-300);flex-grow:1;margin-left:.5rem;padding-top:.25rem}#wp-parsely-dashboard-widget .parsely-top-post-title{color:var(--base-text);font-size:.875rem;margin-right:.4375rem}#wp-parsely-dashboard-widget a.parsely-top-post-title:hover{color:var(--blue-550)}#wp-parsely-dashboard-widget .parsely-top-post-icon-link{position:relative;top:.25rem}#wp-parsely-dashboard-widget .parsely-top-post-icon-link svg{fill:#8d98a1;margin-right:.1875rem}#wp-parsely-dashboard-widget .parsely-top-post-icon-link svg:hover{fill:var(--blue-550)}#wp-parsely-dashboard-widget .parsely-top-post-metadata{margin:.25rem 0 0}#wp-parsely-dashboard-widget .parsely-top-post-metadata>span{color:var(--gray-500)}#wp-parsely-dashboard-widget .parsely-top-post-metadata>span:not(:first-child){margin-left:.75rem}#wp-parsely-dashboard-widget .parsely-post-metric-data{float:right;font-family:var(--numeric-font);font-size:1.125rem;padding-left:.625rem} diff --git a/build/content-helper/dashboard-widget.js b/build/content-helper/dashboard-widget.js index 3aac52622..209dc848a 100644 --- a/build/content-helper/dashboard-widget.js +++ b/build/content-helper/dashboard-widget.js @@ -1 +1 @@ -!function(){"use strict";var e={251:function(e,t,r){var n=r(196),a=Symbol.for("react.element"),s=Symbol.for("react.fragment"),i=Object.prototype.hasOwnProperty,o=n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};function c(e,t,r){var n,s={},c=null,u=null;for(n in void 0!==r&&(c=""+r),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(u=t.ref),t)i.call(t,n)&&!l.hasOwnProperty(n)&&(s[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps)void 0===s[n]&&(s[n]=t[n]);return{$$typeof:a,type:e,key:c,ref:u,props:s,_owner:o.current}}t.Fragment=s,t.jsx=c,t.jsxs=c},893:function(e,t,r){e.exports=r(251)},196:function(e){e.exports=window.React}},t={};function r(n){var a=t[n];if(void 0!==a)return a.exports;var s=t[n]={exports:{}};return e[n](s,s.exports,r),s.exports}r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e,t,n,a=r(893),s=window.wp.element,i=function(e){void 0===e&&(e=null);var t="";(null==e?void 0:e.children)&&(t=e.children);var r="content-helper-error-message";return(null==e?void 0:e.className)&&(r+=" "+e.className),(0,a.jsx)("div",{className:r,"data-testid":null==e?void 0:e.testId,dangerouslySetInnerHTML:{__html:t}})},o=function(e){return void 0===e&&(e=null),(0,a.jsx)(i,{className:null==e?void 0:e.className,testId:"empty-credentials-message",children:window.wpParselyEmptyCredentialsMessage})},l=function(){return l=Object.assign||function(e){for(var t,r=1,n=arguments.length;r0&&a[a.length-1])||6!==o[0]&&2!==o[0])){i=0;continue}if(3===o[0]&&(!a||o[1]>a[0]&&o[1]=1e4&&(clearInterval(s),r("Telemetry library not loaded"))}),100);else r("Telemetry not enabled")}))},e.prototype.trackEvent=function(t,r){var n;this.isLoaded?(0!==t.indexOf(e.TRACKS_PREFIX)&&(t=e.TRACKS_PREFIX+t),this.isEventNameValid(t)?(r=this.prepareProperties(r),null===(n=this._tkq)||void 0===n||n.push(["recordEvent",t,r])):console.error("Error tracking event: Invalid event name")):console.error("Error tracking event: Telemetry not loaded")},e.prototype.isTelemetryEnabled=function(){return this.isEnabled},e.prototype.isProprietyValid=function(t){return e.PROPERTY_REGEX.test(t)},e.prototype.isEventNameValid=function(t){return e.EVENT_NAME_REGEX.test(t)},e.prototype.prepareProperties=function(e){return(e=this.sanitizeProperties(e)).parsely_version=wpParselyTracksTelemetry.version,wpParselyTracksTelemetry.user&&(e._ut=wpParselyTracksTelemetry.user.type,e._ui=wpParselyTracksTelemetry.user.id),wpParselyTracksTelemetry.vipgo_env&&(e.vipgo_env=wpParselyTracksTelemetry.vipgo_env),this.sanitizeProperties(e)},e.prototype.sanitizeProperties=function(e){var t=this,r={};return Object.keys(e).forEach((function(n){t.isProprietyValid(n)&&(r[n]=e[n])})),r},e.TRACKS_PREFIX="wpparsely_",e.EVENT_NAME_REGEX=/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/,e.PROPERTY_REGEX=/^[a-z_][a-z0-9_]*$/,e}(),h=(d.trackEvent,window.wp.apiFetch),f=r.n(h),y=function(e){var t=e.defaultValue,r=e.items,n=e.onChange;return(0,a.jsx)("select",{onChange:n,value:t,children:r.map((function(e){return(0,a.jsx)("option",{value:e[0],children:e[1]},e[0])}))})};!function(e){e.Minutes10="10m",e.Hour="1h",e.Hours2="2h",e.Hours4="4h",e.Hours24="24h",e.Days7="7d",e.Days30="30d"}(e||(e={})),function(e){e.Views="views",e.AvgEngaged="avg_engaged"}(t||(t={})),function(e){e.Author="author",e.Section="section",e.Tag="tag",e.Unavailable="unavailable"}(n||(n={}));var v=function(e,t){return Object.values(t).includes(e)};function w(e,t){void 0===t&&(t=!1);var r=parseInt(e,10),n=e.charAt(e.length-1),a=(0,p.__)("Unknown Period","wp-parsely");switch(n){case"m":if(1===r){a=(0,p.__)("Last Minute","wp-parsely");break}a=(0,p.sprintf)(/* translators: 1: Number of minutes */(0,p._n)("Last %1$d Minute","Last %1$d Minutes",r,"wp-parsely"),r);break;case"h":if(1===r){a=(0,p.__)("Last Hour","wp-parsely");break}a=(0,p.sprintf)(/* translators: 1: Number of hours */(0,p._n)("Last %1$d Hour","Last %1$d Hours",r,"wp-parsely"),r);break;case"d":if(1===r){a=(0,p.__)("Last Day","wp-parsely");break}a=(0,p.sprintf)(/* translators: 1: Number of days */(0,p._n)("Last %1$d Day","Last %1$d Days",r,"wp-parsely"),r)}return t?a.toLocaleLowerCase():a}function _(e){switch(e){case t.Views:return(0,p.__)("Page Views","wp-parsely");case t.AvgEngaged:return(0,p.__)("Avg. Time","wp-parsely");default:return(0,p.__)("Unknown Metric","wp-parsely")}}var m,g,b=window.wp.url,P=(m=function(e,t){return m=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])},m(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function __(){this.constructor=e}m(e,t),e.prototype=null===t?Object.create(t):(__.prototype=t.prototype,new __)});!function(e){e.CannotFormulateApiQuery="ch_cannot_formulate_api_query",e.FetchError="fetch_error",e.HttpRequestFailed="http_request_failed",e[e.ParselyApiForbidden=403]="ParselyApiForbidden",e.ParselyApiResponseContainsError="ch_response_contains_error",e.ParselyApiReturnedNoData="ch_parsely_api_returned_no_data",e.ParselyApiReturnedTooManyResults="ch_parsely_api_returned_too_many_results",e[e.ParselyApiUnauthorized=401]="ParselyApiUnauthorized",e.PluginCredentialsNotSetMessageDetected="parsely_credentials_not_set_message_detected",e.PluginSettingsApiSecretNotSet="parsely_api_secret_not_set",e.PluginSettingsSiteIdNotSet="parsely_site_id_not_set",e.PostIsNotPublished="ch_post_not_published"}(g||(g={}));var x=function(e){function t(r,n,a){void 0===a&&(a=(0,p.__)("Error: ","wp-parsely"));var s=e.call(this,a+r)||this;s.hint=null,s.name=s.constructor.name,s.code=n;var i=[g.ParselyApiForbidden,g.ParselyApiResponseContainsError,g.ParselyApiReturnedNoData,g.ParselyApiReturnedTooManyResults,g.ParselyApiUnauthorized,g.PluginCredentialsNotSetMessageDetected,g.PluginSettingsApiSecretNotSet,g.PluginSettingsSiteIdNotSet,g.PostIsNotPublished];return s.retryFetch=!i.includes(s.code),Object.setPrototypeOf(s,t.prototype),s}return P(t,e),t.prototype.Message=function(e){return void 0===e&&(e=null),[g.PluginCredentialsNotSetMessageDetected,g.PluginSettingsSiteIdNotSet,g.PluginSettingsApiSecretNotSet].includes(this.code)?o(e):(this.code===g.FetchError&&(this.hint=this.Hint((0,p.__)("This error can sometimes be caused by ad-blockers or browser tracking protections. Please add this site to any applicable allow lists and try again.","wp-parsely"))),this.code===g.ParselyApiForbidden&&(this.hint=this.Hint((0,p.__)("Please ensure that the Site ID and API Secret given in the plugin's settings are correct.","wp-parsely"))),this.code===g.HttpRequestFailed&&(this.hint=this.Hint((0,p.__)("The Parse.ly API cannot be reached. Please verify that you are online.","wp-parsely"))),this.code===g.ParselyApiUnauthorized&&(this.message=(0,p.__)("This feature is accessible to select customers participating in its beta testing.","wp-parsely")),(0,a.jsx)(i,{className:null==e?void 0:e.className,testId:"error",children:"

".concat(this.message,"

").concat(this.hint?this.hint:"")}))},t.prototype.Hint=function(e){return'

'.concat((0,p.__)("Hint:","wp-parsely")," ").concat(e,"

")},t}(Error),j={month:"short",day:"numeric",year:"numeric"},E={month:"short",day:"numeric"},N=(0,p.__)("Date N/A","wp-parsely");function T(e){if(!1===function(e){return!isNaN(+e)&&0!==e.getTime()}(e))return N;var t=j;return e.getUTCFullYear()===(new Date).getUTCFullYear()&&(t=E),Intl.DateTimeFormat(document.documentElement.lang||"en",t).format(e)}var k=function(){return k=Object.assign||function(e){for(var t,r=1,n=arguments.length;r0&&a[a.length-1])||6!==o[0]&&2!==o[0])){i=0;continue}if(3===o[0]&&(!a||o[1]>a[0]&&o[1]=c){var u=t;(a=n/c)%1>1/o&&(u=a>10?1:2),u=parseFloat(a.toFixed(2))===parseFloat(a.toFixed(0))?0:u,s=a.toFixed(u),i=l}o=c})),s+r+i}function L(e){var t=e.metric,r=e.post,n=e.avgEngagedIcon,s=e.viewsIcon;return"views"===t?(0,a.jsxs)("span",{className:"parsely-top-post-metric-data",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Number of Views","wp-parsely")}),s,R(r.views.toString())]}):"avg_engaged"===t?(0,a.jsxs)("span",{className:"parsely-top-post-metric-data",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Average Time","wp-parsely")}),n,r.avgEngaged]}):(0,a.jsx)("span",{className:"parsely-top-post-metric-data",children:"-"})}function M(e){var t,r=e.metric,n=e.post;return(0,a.jsx)("li",{className:"parsely-top-post",children:(0,a.jsxs)("div",{className:"parsely-top-post-content",children:[(0,a.jsx)(F,{post:n}),(0,a.jsxs)("div",{className:"parsely-top-post-data",children:[(0,a.jsx)(L,{metric:r,post:n}),(0,a.jsx)(V,{post:n}),(0,a.jsxs)("a",{className:"parsely-top-post-icon-link",href:n.url,target:"_blank",rel:"noreferrer",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("View Post (opens in new tab)","wp-parsely")}),(0,a.jsx)(I,{})]}),0!==n.postId&&(0,a.jsxs)("a",{className:"parsely-top-post-icon-link",href:(t=n.postId,"/wp-admin/post.php?post=".concat(t,"&action=edit")),target:"_blank",rel:"noreferrer",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Edit Post (opens in new tab)","wp-parsely")}),(0,a.jsx)(O,{})]}),(0,a.jsxs)("div",{className:"parsely-top-post-metadata",children:[(0,a.jsxs)("span",{className:"parsely-top-post-date",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Date","wp-parsely")}),T(new Date(n.date))]}),(0,a.jsxs)("span",{className:"parsely-top-post-author",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Author","wp-parsely")}),n.author]})]})]})]})},n.id)}function F(e){var t=e.post;return t.thumbnailUrl?(0,a.jsxs)("div",{className:"parsely-top-post-thumbnail",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Thumbnail","wp-parsely")}),(0,a.jsx)("img",{src:t.thumbnailUrl,alt:(0,p.__)("Post thumbnail","wp-parsely")})]}):(0,a.jsx)("div",{className:"parsely-top-post-thumbnail",children:(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Post thumbnail not available","wp-parsely")})})}function V(e){var t=e.post;return(0,a.jsxs)("a",{className:"parsely-top-post-title",href:t.dashUrl,target:"_blank",rel:"noreferrer",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("View in Parse.ly (opens in new tab)","wp-parsely")}),t.title]})}var D=function(){return D=Object.assign||function(e){for(var t,r=1,n=arguments.length;r0&&a[a.length-1])||6!==o[0]&&2!==o[0])){i=0;continue}if(3===o[0]&&(!a||o[1]>a[0]&&o[1]0&&e.retryFetch?[4,new Promise((function(e){return setTimeout(e,500)}))]:[3,3];case 1:return r.sent(),[4,t(n-1)];case 2:return r.sent(),[3,4];case 3:h(!1),b(e),r.label=4;case 4:return[2]}}))}))})),[2]}))}))};return h(!0),t(1),function(){h(!1),j([]),b(void 0)}}),[i,N]);var k=function(e,t){d.trackEvent("dash_widget_filter_changed",D({filter:e},t))},S=(0,a.jsxs)("div",{className:"parsely-top-posts-filters",children:[(0,a.jsx)(y,{defaultValue:i.Period,items:Object.values(e).map((function(e){return[e,w(e)]})),onChange:function(t){v(t.target.value,e)&&(o(D(D({},i),{Period:t.target.value})),k("period",{period:t.target.value}),T(1))}}),(0,a.jsx)(y,{defaultValue:i.Metric,items:Object.values(t).map((function(e){return[e,_(e)]})),onChange:function(e){v(e.target.value,t)&&(o(D(D({},i),{Metric:e.target.value})),k("metric",{metric:e.target.value}),T(1))}})]}),C=(0,a.jsxs)("div",{className:"parsely-top-posts-navigation",children:[(0,a.jsx)("button",{className:"parsely-top-posts-navigation-prev",disabled:N<=1,"aria-label":(0,p.__)("Previous page","wp-parsely"),onClick:function(){T(N-1),d.trackEvent("dash_widget_navigation",{navigation:"previous",to_page:N-1})},children:(0,p.__)("<< Previous","wp-parsely")}),(0,p.sprintf)(/* translators: 1: Current page */(0,p.__)("Page %1$d","wp-parsely"),N),(0,a.jsx)("button",{className:"parsely-top-posts-navigation-next",disabled:!c&&x.length<5,"aria-label":(0,p.__)("Next page","wp-parsely"),onClick:function(){T(N+1),d.trackEvent("dash_widget_navigation",{navigation:"next",to_page:N+1})},children:(0,p.__)("Next >>","wp-parsely")})]});if(g)return(0,a.jsxs)(a.Fragment,{children:[S,g.Message(),N>1&&C]});var O=(0,a.jsx)("div",{className:"parsely-spinner-wrapper",children:(0,a.jsx)(u.Spinner,{})});return(0,a.jsxs)(a.Fragment,{children:[S,c?O:(0,a.jsx)("ol",{className:"parsely-top-posts",style:{counterReset:"item "+5*(N-1)},children:x.map((function(e){return(0,a.jsx)(M,{metric:i.Metric,post:e},e.id)}))}),(x.length>=5||N>1)&&C]})}window.addEventListener("load",(function(){var e=document.querySelector("#wp-parsely-dashboard-widget > .inside");if(null!==e){var t=(0,a.jsx)(c,{children:(0,a.jsx)(z,{})});s.createRoot?(0,s.createRoot)(e).render(t):(0,s.render)(t,e)}}),!1)}()}(); \ No newline at end of file +!function(){"use strict";var e={251:function(e,t,r){var n=r(196),a=Symbol.for("react.element"),s=Symbol.for("react.fragment"),i=Object.prototype.hasOwnProperty,o=n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};function c(e,t,r){var n,s={},c=null,u=null;for(n in void 0!==r&&(c=""+r),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(u=t.ref),t)i.call(t,n)&&!l.hasOwnProperty(n)&&(s[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps)void 0===s[n]&&(s[n]=t[n]);return{$$typeof:a,type:e,key:c,ref:u,props:s,_owner:o.current}}t.Fragment=s,t.jsx=c,t.jsxs=c},893:function(e,t,r){e.exports=r(251)},196:function(e){e.exports=window.React}},t={};function r(n){var a=t[n];if(void 0!==a)return a.exports;var s=t[n]={exports:{}};return e[n](s,s.exports,r),s.exports}r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e,t,n,a=r(893),s=window.wp.element,i=function(e){void 0===e&&(e=null);var t="";(null==e?void 0:e.children)&&(t=e.children);var r="content-helper-error-message";return(null==e?void 0:e.className)&&(r+=" "+e.className),(0,a.jsx)("div",{className:r,"data-testid":null==e?void 0:e.testId,dangerouslySetInnerHTML:{__html:t}})},o=function(e){return void 0===e&&(e=null),(0,a.jsx)(i,{className:null==e?void 0:e.className,testId:"empty-credentials-message",children:window.wpParselyEmptyCredentialsMessage})},l=function(){return l=Object.assign||function(e){for(var t,r=1,n=arguments.length;r0&&a[a.length-1])||6!==o[0]&&2!==o[0])){i=0;continue}if(3===o[0]&&(!a||o[1]>a[0]&&o[1]=1e4&&(clearInterval(s),r("Telemetry library not loaded"))}),100);else r("Telemetry not enabled")}))},e.prototype.trackEvent=function(t,r){var n;this.isLoaded?(0!==t.indexOf(e.TRACKS_PREFIX)&&(t=e.TRACKS_PREFIX+t),this.isEventNameValid(t)?(r=this.prepareProperties(r),null===(n=this._tkq)||void 0===n||n.push(["recordEvent",t,r])):console.error("Error tracking event: Invalid event name")):console.error("Error tracking event: Telemetry not loaded")},e.prototype.isTelemetryEnabled=function(){return this.isEnabled},e.prototype.isProprietyValid=function(t){return e.PROPERTY_REGEX.test(t)},e.prototype.isEventNameValid=function(t){return e.EVENT_NAME_REGEX.test(t)},e.prototype.prepareProperties=function(e){return(e=this.sanitizeProperties(e)).parsely_version=wpParselyTracksTelemetry.version,wpParselyTracksTelemetry.user&&(e._ut=wpParselyTracksTelemetry.user.type,e._ui=wpParselyTracksTelemetry.user.id),wpParselyTracksTelemetry.vipgo_env&&(e.vipgo_env=wpParselyTracksTelemetry.vipgo_env),this.sanitizeProperties(e)},e.prototype.sanitizeProperties=function(e){var t=this,r={};return Object.keys(e).forEach((function(n){t.isProprietyValid(n)&&(r[n]=e[n])})),r},e.TRACKS_PREFIX="wpparsely_",e.EVENT_NAME_REGEX=/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/,e.PROPERTY_REGEX=/^[a-z_][a-z0-9_]*$/,e}(),f=(d.trackEvent,function(e){var t=e.defaultValue,r=e.items,n=e.onChange;return(0,a.jsx)("select",{onChange:n,value:t,children:r.map((function(e){return(0,a.jsx)("option",{value:e[0],children:e[1]},e[0])}))})}),h=window.wp.data,y=function(){return y=Object.assign||function(e){for(var t,r=1,n=arguments.length;rhere.',"wp-parsely"):s.code===N.ParselyInternalServerError?s.message=(0,p.__)("The Parse.ly API returned an internal server error. Please try again later.","wp-parsely"):s.code===N.HttpRequestFailed&&s.message.includes("cURL error 28")?s.message=(0,p.__)("The Parse.ly API did not respond in a timely manner. Please try again later.","wp-parsely"):s.code===N.ParselySchemaValidationFailed?s.message=(0,p.__)("The Parse.ly API returned a validation error. Please try again later.","wp-parsely"):s.code===N.ParselyUpstreamMalformedResponse&&s.message.includes("Insufficient Storage")?s.message=(0,p.__)("The Parse.ly API couldn't find any relevant data to fulfill the request. Please retry with a different input.","wp-parsely"):s.code===N.ParselyUpstreamMalformedResponse?s.message=(0,p.__)("The Parse.ly API returned a malformed response. Please try again later.","wp-parsely"):s.code===N.ParselyUpstreamNotAvailable&&(s.message=(0,p.__)("The Parse.ly API is currently unavailable. Please try again later.","wp-parsely")),s}return I(t,e),t.prototype.Message=function(e){return void 0===e&&(e=null),[N.PluginCredentialsNotSetMessageDetected,N.PluginSettingsSiteIdNotSet,N.PluginSettingsApiSecretNotSet].includes(this.code)?o(e):(this.code===N.FetchError&&(this.hint=this.Hint((0,p.__)("This error can sometimes be caused by ad-blockers or browser tracking protections. Please add this site to any applicable allow lists and try again.","wp-parsely"))),this.code===N.ParselyApiForbidden&&(this.hint=this.Hint((0,p.__)("Please ensure that the Site ID and API Secret given in the plugin's settings are correct.","wp-parsely"))),this.code===N.HttpRequestFailed&&(this.hint=this.Hint((0,p.__)("The Parse.ly API cannot be reached. Please verify that you are online.","wp-parsely"))),(0,a.jsx)(i,{className:null==e?void 0:e.className,testId:"error",children:"

".concat(this.message,"

").concat(this.hint?this.hint:"")}))},t.prototype.Hint=function(e){return'

'.concat((0,p.__)("Hint:","wp-parsely")," ").concat(e,"

")},t}(Error),C={month:"short",day:"numeric",year:"numeric"},R={month:"short",day:"numeric"},O=(0,p.__)("Date N/A","wp-parsely");function L(e){if(!1===function(e){return!isNaN(+e)&&0!==e.getTime()}(e))return O;var t=C;return e.getUTCFullYear()===(new Date).getUTCFullYear()&&(t=R),Intl.DateTimeFormat(document.documentElement.lang||"en",t).format(e)}var M=function(){return M=Object.assign||function(e){for(var t,r=1,n=arguments.length;r0&&a[a.length-1])||6!==o[0]&&2!==o[0])){i=0;continue}if(3===o[0]&&(!a||o[1]>a[0]&&o[1]=c){var u=t;(a=n/c)%1>1/o&&(u=a>10?1:2),u=parseFloat(a.toFixed(2))===parseFloat(a.toFixed(0))?0:u,s=a.toFixed(u),i=l}o=c})),s+r+i}function q(e){var t=e.metric,r=e.post,n=e.avgEngagedIcon,s=e.viewsIcon;return"views"===t?(0,a.jsxs)("span",{className:"parsely-post-metric-data",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Number of Views","wp-parsely")}),s,G(r.views.toString())]}):"avg_engaged"===t?(0,a.jsxs)("span",{className:"parsely-post-metric-data",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Average Time","wp-parsely")}),n,r.avgEngaged]}):(0,a.jsx)("span",{className:"parsely-post-metric-data",children:"-"})}function z(e){var t,r=e.metric,n=e.post;return(0,a.jsx)("li",{className:"parsely-top-post",children:(0,a.jsxs)("div",{className:"parsely-top-post-content",children:[(0,a.jsx)($,{post:n}),(0,a.jsxs)("div",{className:"parsely-top-post-data",children:[(0,a.jsx)(q,{metric:r,post:n}),(0,a.jsx)(X,{post:n}),(0,a.jsxs)("a",{className:"parsely-top-post-icon-link",href:n.url,target:"_blank",rel:"noreferrer",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("View Post (opens in new tab)","wp-parsely")}),(0,a.jsx)(D,{})]}),0!==n.postId&&(0,a.jsxs)("a",{className:"parsely-top-post-icon-link",href:(t=n.postId,"/wp-admin/post.php?post=".concat(t,"&action=edit")),target:"_blank",rel:"noreferrer",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Edit Post (opens in new tab)","wp-parsely")}),(0,a.jsx)(H,{})]}),(0,a.jsxs)("div",{className:"parsely-top-post-metadata",children:[(0,a.jsxs)("span",{className:"parsely-top-post-date",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Date","wp-parsely")}),L(new Date(n.date))]}),(0,a.jsxs)("span",{className:"parsely-top-post-author",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Author","wp-parsely")}),n.author]})]})]})]})},n.id)}function $(e){var t=e.post;return t.thumbnailUrl?(0,a.jsxs)("div",{className:"parsely-top-post-thumbnail",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Thumbnail","wp-parsely")}),(0,a.jsx)("img",{src:t.thumbnailUrl,alt:(0,p.__)("Post thumbnail","wp-parsely")})]}):(0,a.jsx)("div",{className:"parsely-top-post-thumbnail",children:(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Post thumbnail not available","wp-parsely")})})}function X(e){var t=e.post;return(0,a.jsxs)("a",{className:"parsely-top-post-title",href:t.dashUrl,target:"_blank",rel:"noreferrer",children:[(0,a.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("View in Parse.ly (opens in new tab)","wp-parsely")}),t.title]})}var Y=function(){return Y=Object.assign||function(e){for(var t,r=1,n=arguments.length;r0&&a[a.length-1])||6!==o[0]&&2!==o[0])){i=0;continue}if(3===o[0]&&(!a||o[1]>a[0]&&o[1]0&&e.retryFetch?[4,new Promise((function(e){return setTimeout(e,500)}))]:[3,3];case 1:return r.sent(),[4,t(n-1)];case 2:return r.sent(),[3,4];case 3:h(!1),w(e),r.label=4;case 4:return[2]}}))}))})),[2]}))}))};return h(!0),t(1),function(){h(!1),_([]),w(void 0)}}),[i,x]);var N=function(e,t){d.trackEvent("dash_widget_filter_changed",Y({filter:e},t))},k=(0,a.jsxs)("div",{className:"parsely-top-posts-filters",children:[(0,a.jsx)(f,{defaultValue:i.Period,items:Object.values(e).map((function(e){return[e,j(e)]})),onChange:function(t){S(t.target.value,e)&&(o({Period:t.target.value}),N("period",{period:t.target.value}),T(1))}}),(0,a.jsx)(f,{defaultValue:i.Metric,items:Object.values(t).map((function(e){return[e,E(e)]})),onChange:function(e){S(e.target.value,t)&&(o({Metric:e.target.value}),N("metric",{metric:e.target.value}),T(1))}})]}),I=(0,a.jsxs)("div",{className:"parsely-top-posts-navigation",children:[(0,a.jsx)("button",{className:"parsely-top-posts-navigation-prev",disabled:x<=1,"aria-label":(0,p.__)("Previous page","wp-parsely"),onClick:function(){T(x-1),d.trackEvent("dash_widget_navigation",{navigation:"previous",to_page:x-1})},children:(0,p.__)("<< Previous","wp-parsely")}),(0,p.sprintf)(/* translators: 1: Current page */(0,p.__)("Page %1$d","wp-parsely"),x),(0,a.jsx)("button",{className:"parsely-top-posts-navigation-next",disabled:!c&&m.length<5,"aria-label":(0,p.__)("Next page","wp-parsely"),onClick:function(){T(x+1),d.trackEvent("dash_widget_navigation",{navigation:"next",to_page:x+1})},children:(0,p.__)("Next >>","wp-parsely")})]});if(v)return(0,a.jsxs)(a.Fragment,{children:[k,v.Message(),x>1&&I]});var A=(0,a.jsx)("div",{className:"parsely-spinner-wrapper",children:(0,a.jsx)(u.Spinner,{})});return(0,a.jsxs)(a.Fragment,{children:[k,c?A:(0,a.jsx)("ol",{className:"parsely-top-posts",style:{counterReset:"item "+5*(x-1)},children:m.map((function(e){return(0,a.jsx)(z,{metric:i.Metric,post:e},e.id)}))}),(m.length>=5||x>1)&&I]})}var K=function(r){var n;try{n=JSON.parse(r)}catch(r){return{Metric:t.Views,Period:e.Days7}}return S(null==n?void 0:n.Metric,t)||(n.Metric=t.Views),S(null==n?void 0:n.Period,e)||(n.Period=e.Days7),n};window.addEventListener("load",(function(){var e=document.querySelector("#wp-parsely-dashboard-widget > .inside");if(null!==e){var t=(0,a.jsx)(x,{endpoint:"dashboard-widget-settings",defaultSettings:K(window.wpParselyContentHelperSettings),children:(0,a.jsx)(c,{children:(0,a.jsx)(W,{})})});s.createRoot?(0,s.createRoot)(e).render(t):(0,s.render)(t,e)}}),!1)}()}(); \ No newline at end of file diff --git a/build/content-helper/editor-sidebar.asset.php b/build/content-helper/editor-sidebar.asset.php index 0a98b64b5..4a4401c4c 100644 --- a/build/content-helper/editor-sidebar.asset.php +++ b/build/content-helper/editor-sidebar.asset.php @@ -1 +1 @@ - array('react', 'wp-api-fetch', 'wp-components', 'wp-core-data', 'wp-data', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-plugins', 'wp-primitives', 'wp-url'), 'version' => 'c5cfd2f2ca1a40f7169c'); + array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-plugins', 'wp-primitives', 'wp-url'), 'version' => '135fcbc9d535df4d3f4c'); diff --git a/build/content-helper/editor-sidebar.css b/build/content-helper/editor-sidebar.css index dd1083259..a1fe4648f 100644 --- a/build/content-helper/editor-sidebar.css +++ b/build/content-helper/editor-sidebar.css @@ -1,2 +1,6 @@ -.wp-parsely-beta-badge{align-self:end;background-color:var(--parsely-green);border-radius:.34em;box-shadow:0 .0625rem .1875rem rgba(0,0,0,.2);color:#fff;display:inline-block;font-size:.75rem;font-weight:700;margin:.125em .125em .125em auto;opacity:.8;padding:.15em .65em;text-transform:uppercase;-webkit-user-select:none;-moz-user-select:none;user-select:none}.parsely-tone-selector-dropdown{margin-bottom:.625rem;width:100%}.parsely-tone-selector-dropdown.is-disabled{opacity:.5;pointer-events:none}.parsely-tone-selector-dropdown .components-dropdown-menu__toggle{display:flex;gap:.625rem;width:100%}.parsely-tone-selector-dropdown .components-dropdown-menu__toggle svg:first-of-type path{transform:scale(1.4);transform-origin:center}.parsely-tone-selector-dropdown .parsely-tone-selector-label{flex-grow:2;text-align:left}.parsely-tone-selector-dropdown .parsely-tone-selector-label:first-letter{text-transform:uppercase}#wp-parsely-dashboard-widget,.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e}.parsely-persona-selector-dropdown{margin-bottom:.625rem;width:100%}.parsely-persona-selector-dropdown.is-disabled{opacity:.5;pointer-events:none}.parsely-persona-selector-dropdown .components-dropdown-menu__toggle{display:flex;gap:.625rem;width:100%}.parsely-persona-selector-dropdown .parsely-persona-selector-label{flex-grow:2;text-align:left}.parsely-persona-selector-dropdown .parsely-persona-selector-label:first-letter{text-transform:uppercase}.wp-parsely-popover button.components-button.components-menu-item__button.is-selected,.wp-parsely-popover button.components-button.components-menu-item__button.is-selected:focus{box-shadow:0 0 0 2px var(--parsely-green);outline:3px solid transparent} -#wp-parsely-dashboard-widget,.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e}.wp-parsely-content-helper .parsely-spinner-wrapper{display:flex;justify-content:center;margin:2.5rem 0}.wp-parsely-content-helper .parsely-spinner-wrapper svg{height:22px;width:22px}.wp-parsely-content-helper .content-helper-error-message{margin-top:.9375rem!important}.wp-parsely-content-helper p.content-helper-error-message-hint{color:var(--gray-700)}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-posts{list-style-type:none;margin:1.375rem 0 0}.wp-parsely-content-helper .parsely-top-posts-wrapper p.parsely-top-posts-descr{color:var(--gray-700);margin-top:.9375rem}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post{border-top:1px solid var(--gray-300);margin-bottom:.3125rem;padding:.625rem 0}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-title a{line-height:16px;text-decoration:none}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-stats-link{color:var(--black);font-size:.875rem;margin-right:.4375rem}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-stats-link:hover{color:var(--blue-550)}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-metric-data span.dashicon,.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-metric-data svg{fill:var(--gray-600);margin-right:.1875rem;position:relative;top:2px}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-metric-data span.dashicon{color:var(--gray-600);font-size:16px;height:16px;width:16px}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-edit-link,.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-view-link{display:inline-block;height:16px;margin-right:.1875rem;position:relative;width:16px}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-edit-link svg,.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-view-link svg{fill:#8d98a1;position:absolute;top:2px}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-edit-link:hover svg,.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-view-link:hover svg{fill:var(--blue-550)}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-info{align-items:center;display:flex;justify-content:space-between;margin:.3125rem 0 0}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-info>span{color:var(--gray-600);display:flex;margin-bottom:0}.wp-parsely-content-helper .parsely-top-posts-wrapper .parsely-top-post-info>span:not(:first-child){margin-left:.3125rem}.wp-parsely-content-helper .performance-details-panel div.section{font-family:var(--base-font);margin-top:1.8rem}.wp-parsely-content-helper .performance-details-panel div.section table{border-collapse:collapse;width:100%}.wp-parsely-content-helper .performance-details-panel div.section table th{font-weight:400;text-align:left}.wp-parsely-content-helper .performance-details-panel div.section div.section-title{color:var(--base-text-2);margin-bottom:.5rem}.wp-parsely-content-helper .performance-details-panel div.section.period{color:var(--base-text-2);margin-top:.8rem}.wp-parsely-content-helper .performance-details-panel div.section.general-performance table tbody tr{font-family:var(--numeric-font);font-size:var(--font-size--extra-large);font-weight:500}.wp-parsely-content-helper .performance-details-panel div.section.general-performance table tfoot tr{color:var(--gray-700);height:1.4rem;vertical-align:bottom}.wp-parsely-content-helper .performance-details-panel div.section.referrer-types div.multi-percentage-bar{--radius:2px;display:flex;height:.5rem}.wp-parsely-content-helper .performance-details-panel div.section.referrer-types div.multi-percentage-bar .bar-fill:first-child{border-radius:var(--radius) 0 0 var(--radius)}.wp-parsely-content-helper .performance-details-panel div.section.referrer-types div.multi-percentage-bar .bar-fill:last-child{border-radius:0 var(--radius) var(--radius) 0}.wp-parsely-content-helper .performance-details-panel div.section.referrer-types div.multi-percentage-bar .bar-fill.direct{background-color:hsl(var(--ref-direct))}.wp-parsely-content-helper .performance-details-panel div.section.referrer-types div.multi-percentage-bar .bar-fill.internal{background-color:hsl(var(--ref-internal))}.wp-parsely-content-helper .performance-details-panel div.section.referrer-types div.multi-percentage-bar .bar-fill.search{background-color:hsl(var(--ref-search))}.wp-parsely-content-helper .performance-details-panel div.section.referrer-types div.multi-percentage-bar .bar-fill.social{background-color:hsl(var(--ref-social))}.wp-parsely-content-helper .performance-details-panel div.section.referrer-types div.multi-percentage-bar .bar-fill.other{background-color:hsl(var(--ref-other))}.wp-parsely-content-helper .performance-details-panel div.section.referrer-types table{margin-top:.5rem}.wp-parsely-content-helper .performance-details-panel div.section.referrer-types table tbody tr{font-family:var(--numeric-font);font-size:var(--font-size--large);height:1.4rem;vertical-align:bottom}.wp-parsely-content-helper .performance-details-panel div.section.top-referrers table thead tr{color:var(--base-text-2);height:1.6rem;vertical-align:top}.wp-parsely-content-helper .performance-details-panel div.section.top-referrers table thead tr th:last-child{text-align:right}.wp-parsely-content-helper .performance-details-panel div.section.top-referrers table tbody tr{border:1px solid var(--border);border-left:0;border-right:0;height:2rem}.wp-parsely-content-helper .performance-details-panel div.section.top-referrers table tbody tr th:first-child{--width:8rem;max-width:var(--width);min-width:var(--width);overflow:hidden;padding-right:1rem;text-overflow:ellipsis;white-space:nowrap}.wp-parsely-content-helper .performance-details-panel div.section.top-referrers table tbody tr td:nth-child(2){width:100%}.wp-parsely-content-helper .performance-details-panel div.section.top-referrers table tbody tr td:last-child{padding-left:1rem;text-align:right}.wp-parsely-content-helper .performance-details-panel div.section.top-referrers table tbody div.percentage-bar{--radius:4px;background-color:var(--base-3);border-radius:var(--radius);display:flex;height:.4rem;margin:0;overflow:hidden}.wp-parsely-content-helper .performance-details-panel div.section.top-referrers table tbody div.percentage-bar:after{background-color:var(--data);border-radius:var(--radius);content:"";height:100%;width:var(--bar-fill)}.wp-parsely-content-helper .performance-details-panel div.section.top-referrers div:last-child{color:var(--base-text-2);margin-top:.6rem}.wp-parsely-content-helper .performance-details-panel div.section.actions{display:inline-flex;justify-content:space-between;width:100%}.wp-parsely-content-helper .performance-details-panel div.section.actions a.components-button{border-radius:4px;text-transform:uppercase}.wp-parsely-content-helper .performance-details-panel div.section.actions a.components-button.is-secondary{box-shadow:inset 0 0 0 1px var(--border);color:var(--sidebar-black)}.wp-parsely-content-helper .performance-details-panel div.section.actions a.components-button.is-primary{background-color:var(--control)}.wp-parsely-content-helper .parsely-write-titles-wrapper{display:flex;flex-direction:column}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-text{margin-bottom:.625rem}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-text strong{text-transform:lowercase}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-generate-button{display:flex;justify-content:center;margin:.625rem 0}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-settings .parsely-write-titles-settings-header{display:flex;margin:.625rem 0;width:100%}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-settings .parsely-write-titles-settings-header .parsely-write-titles-settings-header-label{flex-grow:2;margin-left:.3125rem;text-align:left}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-settings .parsely-write-titles-settings-header .parsely-write-titles-settings-header-label .components-base-control__field{align-items:center;display:flex;justify-content:space-between}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-settings .parsely-write-titles-settings-header .parsely-write-titles-settings-header-label .components-base-control__field .components-base-control__label{margin-bottom:0}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-accepted-title-container .parsely-write-titles-accepted-title{font-size:1rem;font-weight:600;line-height:1.25rem;margin:0 0 .9375rem;text-align:center}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-accepted-title-container .parsely-write-titles-accepted-title-actions{display:flex;gap:.625rem;justify-content:center;margin:.625rem 0}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-title-suggestions-container{margin:.625rem 0 0}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-title-suggestions-container .parsely-write-titles-title-suggestion{border-top:1px solid var(--gray-300);display:flex;padding:1rem 0}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-title-suggestions-container .parsely-write-titles-title-suggestion .parsely-write-titles-suggested-title{flex:1;font-size:.875rem;font-weight:600;padding:0 .5rem 0 .3125rem}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-title-suggestions-container .parsely-write-titles-title-suggestion .parsely-write-titles-suggested-title-actions{display:flex;flex-direction:column;width:1.5625rem}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-title-suggestions-container .parsely-write-titles-title-suggestion .parsely-write-titles-suggested-title-actions .components-button-group .components-button{margin-left:0}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-title-suggestions-container .parsely-write-titles-title-suggestion .parsely-write-titles-suggested-title-actions .is-pinned{background-color:var(--gray-500);box-shadow:inset 0 0 0 1px var(--sidebar-black);color:#fff}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-title-suggestions-container .parsely-write-titles-title-suggestion.original-title{background-color:var(--gray-200);border:0;color:var(--gray-600);margin-bottom:.9375rem;padding:.3125rem}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-title-suggestions-container .parsely-write-titles-title-suggestion.original-title .parsely-write-titles-suggested-title{padding-bottom:.3125rem;padding-top:.3125rem}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-title-suggestions-container .parsely-write-titles-title-suggestion.original-title button{margin-top:.125rem}.wp-parsely-content-helper .parsely-write-titles-wrapper .parsely-write-titles-title-suggestions-container .title-in-use{color:var(--green-900)} +.wp-parsely-beta-badge{align-self:end;background-color:var(--parsely-green);border-radius:.34em;box-shadow:0 .0625rem .1875rem rgba(0,0,0,.2);color:#fff;display:inline-block;font-size:.75rem;font-weight:700;margin:.125em .125em .125em auto;opacity:.8;padding:.15em .65em;text-transform:uppercase;-webkit-user-select:none;-moz-user-select:none;user-select:none}.parsely-tone-selector-dropdown{align-items:center;align-self:stretch;background:var(--Gutenberg-White,#fff);border:1px solid var(--Gutenberg-Gray-600,#949494);border-radius:2px;gap:var(--grid-unit-05,.25rem);height:2.25rem;overflow-wrap:break-word;width:100%;word-break:break-word}.parsely-tone-selector-dropdown button{height:2.0625rem}.parsely-tone-selector-dropdown .components-dropdown-menu__toggle.has-icon svg:first-child{display:none}.parsely-tone-selector-dropdown.is-disabled{opacity:.5;pointer-events:none}.parsely-tone-selector-dropdown .components-dropdown-menu__toggle{display:flex;gap:.625rem;width:100%}.parsely-tone-selector-dropdown .components-dropdown-menu__toggle svg:first-of-type path{transform:scale(1.4);transform-origin:center}.parsely-tone-selector-dropdown .parsely-tone-selector-label{flex-grow:2;padding:0 var(--grid-unit-10,.5rem);text-align:left}.parsely-tone-selector-dropdown .parsely-tone-selector-label:first-letter{text-transform:uppercase}.parsely-persona-selector-custom{width:100%}.parsely-persona-selector-custom .components-base-control__field input{display:flex;height:2.5rem;padding:var(--grid-unit-15,.75rem) var(--grid-unit-20,1rem)}.parsely-persona-selector-custom .components-base-control__field input,.parsely-persona-selector-dropdown{align-items:center;align-self:stretch;border:1px solid var(--Gutenberg-Gray-600,#949494);border-radius:2px;gap:var(--grid-unit-05,.25rem)}.parsely-persona-selector-dropdown{background:var(--Gutenberg-White,#fff);height:2.25rem;overflow-wrap:break-word;width:100%;word-break:break-word}.parsely-persona-selector-dropdown .components-dropdown-menu__toggle.has-icon svg:first-child{display:none}.parsely-persona-selector-dropdown button{height:2.0625rem}.parsely-persona-selector-dropdown.is-disabled{opacity:.5;pointer-events:none}.parsely-persona-selector-dropdown .components-dropdown-menu__toggle{display:flex;gap:.625rem;width:100%}.parsely-persona-selector-dropdown .parsely-persona-selector-label{flex-grow:2;padding:0 var(--grid-unit-10,.5rem);text-align:left}.parsely-persona-selector-dropdown .parsely-persona-selector-label:first-letter{text-transform:uppercase}.parsely-tone-selector-custom{width:100%}.parsely-tone-selector-custom .components-base-control__field input{align-items:center;align-self:stretch;border:1px solid var(--Gutenberg-Gray-600,#949494);border-radius:2px;display:flex;gap:var(--grid-unit-05,.25rem);height:2.5rem;padding:var(--grid-unit-15,.75rem) var(--grid-unit-20,1rem)}#wp-parsely-dashboard-widget,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"],.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-panel,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--parsely-green-10:#c7ecb1;--parsely-green-65:#2a691b;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--grid-unit-5:0.25rem;--grid-unit-10:0.5rem;--grid-unit-15:0.75rem;--grid-unit-20:1rem;--font-size--smaller:0.688rem;--font-size--small:0.75rem;--font-size--medium:0.875rem;--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e;--sidebar-white:#f0f0f0}.parsely-inputrange-control{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:var(--grid-unit-10,.5rem)}.parsely-inputrange-control .parsely-inputrange-control__label{margin:0}.parsely-inputrange-control .parsely-inputrange-control__controls{align-items:center;align-self:stretch;display:flex;gap:var(--grid-unit-20,1rem);height:2.5rem}.parsely-inputrange-control .parsely-inputrange-control__controls .components-input-control{display:flex;flex:1 0 0}.parsely-inputrange-control .parsely-inputrange-control__controls .components-input-control input[type=number]::-webkit-inner-spin-button,.parsely-inputrange-control .parsely-inputrange-control__controls .components-input-control input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.parsely-inputrange-control .parsely-inputrange-control__controls .components-input-control input[type=number]{-moz-appearance:textfield}.parsely-inputrange-control .parsely-inputrange-control__controls .components-input-control .components-base-control__field{flex-grow:1}.parsely-inputrange-control .parsely-inputrange-control__controls .components-input-control .components-input-control__suffix{color:var(--wp-components-color-accent,var(--wp-admin-theme-color,#3858e9))}.parsely-inputrange-control .parsely-inputrange-control__controls .components-range-control{flex:1 0 0;height:36px}.parsely-inputrange-control .parsely-inputrange-control__controls .components-range-control .components-range-control__root{height:2.5rem}.parsely-panel-settings{width:100%}.parsely-panel-settings .parsely-panel-settings-header{display:flex;margin:.625rem 0;width:100%}.parsely-panel-settings .parsely-panel-settings-header .parsely-panel-settings-header-label{flex-grow:2;margin:0 0 0 .3125rem;text-align:left}.parsely-panel-settings .parsely-panel-settings-header .parsely-panel-settings-header-label .components-base-control__field{align-items:center;display:flex;justify-content:space-between}.parsely-panel-settings .parsely-panel-settings-header .parsely-panel-settings-header-label .components-base-control__field .components-base-control__label{margin-bottom:0}.parsely-panel-settings .parsely-panel-settings-body{align-self:stretch;display:flex;flex-direction:column;gap:.625rem;padding:.375rem 0 var(--grid-unit-20,1rem) 0}.wp-parsely-content-helper-error{align-self:flex-start;margin:0}.wp-parsely-content-helper-error p{margin:0}.wp-parsely-content-helper-error .content-helper-error-message{margin:0!important}.wp-parsely-collapsible-panel{align-items:flex-start;align-self:stretch;border-bottom:none;border-radius:2px;border-top:none;display:flex;flex-direction:column;gap:var(--grid-unit-20,1rem);margin:0 0 .5rem;padding:0}.wp-parsely-collapsible-panel .components-panel__body-title{align-items:center;align-self:stretch;display:flex;gap:var(--grid-unit-10,.5rem);margin:0 -1rem .375rem;padding:0}.wp-parsely-collapsible-panel .components-panel__body-title .components-panel__icon,.wp-parsely-collapsible-panel .components-panel__body-title svg{margin-left:0;margin-right:var(--grid-unit-10,.5rem);order:-1}.wp-parsely-collapsible-panel .components-panel__body-title:hover{background-color:transparent}.wp-parsely-collapsible-panel.is-opened{padding:0}.wp-parsely-collapsible-panel.is-opened>div:first-of-type{margin-top:calc(var(--grid-unit-20, 1rem)*-1)}.wp-parsely-collapsible-panel.is-opened .components-panel__body-title{margin:0 calc(var(--grid-unit-20, 1rem)*-1) .375rem;padding-bottom:0}.wp-parsely-collapsible-panel .components-panel__body-toggle.components-button{color:var(--Gutenberg-Gray-900,#1e1e1e);font-size:.6875rem;font-style:normal;font-weight:600;line-height:1rem;padding:var(--grid-unit-20,1rem) var(--grid-unit-20,1rem);text-transform:uppercase}.wp-parsely-collapsible-panel .components-panel__body-toggle.components-button .components-panel__arrow{margin-right:0}.wp-parsely-collapsible-panel .components-panel__body.is-opened{padding:0} +#wp-parsely-dashboard-widget,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"],.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-panel,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--parsely-green-10:#c7ecb1;--parsely-green-65:#2a691b;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--grid-unit-5:0.25rem;--grid-unit-10:0.5rem;--grid-unit-15:0.75rem;--grid-unit-20:1rem;--font-size--smaller:0.688rem;--font-size--small:0.75rem;--font-size--medium:0.875rem;--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e;--sidebar-white:#f0f0f0}.wp-parsely-block-overlay{align-items:center;background-color:hsla(0,0%,100%,.85);box-sizing:border-box;display:flex;height:100%;justify-content:center;left:0;position:absolute;top:0;width:100%}.wp-parsely-block-overlay .wp-parsely-block-overlay-label{align-items:center;display:flex;flex-direction:column;flex-grow:1;justify-content:center;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.wp-parsely-block-overlay svg{height:1.5625rem;width:1.5625rem}.wp-parsely-block-overlay.full-content-overlay{font-size:1.25rem;z-index:999}.wp-parsely-block-overlay.full-content-overlay span{margin-top:.9375rem}.wp-parsely-block-overlay.full-content-overlay svg{height:3.125rem;width:3.125rem}.wp-parsely-panel .wp-parsely-icon{margin-right:.3125rem;order:-1}.wp-parsely-smart-linking .components-panel__row{flex-direction:column;margin-bottom:0}.wp-parsely-smart-linking .components-base-control,.wp-parsely-smart-linking .components-base-control .components-panel__row,.wp-parsely-smart-linking .components-base-control:last-child{margin-bottom:0}.wp-parsely-smart-linking .smart-linking-text{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:.625rem;padding:.375rem 0 var(--grid-unit-20,1rem) 0}.wp-parsely-smart-linking .parsely-panel-settings-body .smart-linking-block-select{align-self:stretch;display:flex;flex-direction:column;gap:1.5rem;padding:.375rem 0 var(--grid-unit-20,1rem) 0}.wp-parsely-smart-linking .parsely-panel-settings-body .smart-linking-block-select .components-toggle-group-control{height:40px;overflow:hidden}.wp-parsely-smart-linking .parsely-panel-settings-body .smart-linking-block-select .components-toggle-group-control button{background:transparent;outline:2px solid transparent;outline-offset:-3px}.wp-parsely-smart-linking .parsely-panel-settings-body .smart-linking-block-select .components-toggle-group-control button[data-active-item]{background:var(--sidebar-black,#1e1e1e);border-radius:3px;box-shadow:0 -3px 0 0 #fff,0 3px 0 0 #fff;transition:background 0s .5s,border-radius 0s .5s,box-shadow 0s .5s}.wp-parsely-smart-linking .parsely-panel-settings-body .smart-linking-settings{align-self:stretch;display:flex;flex-direction:column;gap:var(--grid-unit-20,1rem)}.wp-parsely-smart-linking .smart-linking-generate{align-self:stretch;display:flex;flex-direction:column;padding:.375rem 0 var(--grid-unit-20,1rem) 0}.wp-parsely-smart-linking .smart-linking-generate button{align-items:center;align-self:stretch;display:flex;justify-content:center;width:100%}.wp-parsely-smart-linking .wp-parsely-smart-linking-hint,.wp-parsely-smart-linking .wp-parsely-smart-linking-suggested-links{margin:0} +#wp-parsely-dashboard-widget,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"],.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-panel,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--parsely-green-10:#c7ecb1;--parsely-green-65:#2a691b;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--grid-unit-5:0.25rem;--grid-unit-10:0.5rem;--grid-unit-15:0.75rem;--grid-unit-20:1rem;--font-size--smaller:0.688rem;--font-size--small:0.75rem;--font-size--medium:0.875rem;--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e;--sidebar-white:#f0f0f0}.wp-parsely-content-helper .wp-parsely-performance-panel{border-top:1px solid #ddd;margin-top:-1px;padding:16px}.wp-parsely-content-helper .wp-parsely-performance-panel .components-button.wp-parsely-view-post{align-items:center;display:flex;justify-content:center;width:100%}.wp-parsely-content-helper .wp-parsely-performance-panel .panel-body{width:100%}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-stat-panel{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:.5rem;padding:.375rem 0 1rem}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-stat-panel .panel-header{align-items:center;display:flex;gap:.375rem;height:1rem;margin-bottom:0;width:100%}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-stat-panel .panel-header.level-2{margin-bottom:.9375rem}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-stat-panel .panel-header.level-2 h2{margin-bottom:0}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-stat-panel .panel-header.level-3 h3{font-size:var(--font-size--smaller,.6875rem);font-style:normal;font-weight:600;line-height:1rem;margin-bottom:0;text-transform:uppercase}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-stat-panel .panel-subtitle{align-items:center;align-self:stretch;color:var(--Gutenberg-Gray-700,#757575);display:flex;flex:1 0 0;font-size:var(--font-size--smaller,.6875rem);font-style:normal;font-weight:600;gap:.375rem;height:1rem;line-height:1rem;text-transform:uppercase}.wp-parsely-content-helper .wp-parsely-performance-panel .components-heading{display:block;font-weight:500;line-height:normal;margin:0}.wp-parsely-content-helper .wp-parsely-performance-panel .components-dropdown-menu{line-height:0}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-data-points{align-content:flex-start;align-items:flex-start;align-self:stretch;display:flex;flex-wrap:wrap;gap:1rem var(--grid-unit-20,1rem)}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-data-points .data-point{align-items:center;background:var(--sidebar-white,#f0f0f0);border-radius:.25rem;display:flex;flex:1 0 0;flex-direction:column;gap:var(--grid-unit-05,.25rem);justify-content:center;min-width:100px;padding:var(--grid-unit-20,1rem) var(--grid-unit-10,.5rem)}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-data-points .data-point svg{align-items:flex-start;border-radius:.125rem;display:flex}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-data-points .data-point .data-point-title{align-self:stretch;color:var(--sidebar-black,#1e1e1e);font-size:.75rem;font-style:normal;font-weight:400;line-height:1rem;text-align:center}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-data-points .data-point .data-point-value{align-self:stretch;color:var(--sidebar-black,#1e1e1e);font-size:1.25rem;font-style:normal;font-weight:590;line-height:1.75rem;text-align:center}.wp-parsely-content-helper .wp-parsely-performance-panel .performance-data-points .data-point .data-point-value.is-small{font-size:var(--font-size--medium,.875rem)}.wp-parsely-content-helper .wp-parsely-performance-panel div.multi-percentage-bar{align-items:flex-start;align-self:stretch;display:flex;height:1rem;margin:1rem 0;position:relative}.wp-parsely-content-helper .wp-parsely-performance-panel div.multi-percentage-bar .bar-fill{--radius:2px;align-self:stretch;position:relative;transition:filter .1s ease,transform .1s ease}.wp-parsely-content-helper .wp-parsely-performance-panel div.multi-percentage-bar .bar-fill:hover{filter:opacity(1)!important;transform:scaleX(1) scaleY(1)!important}.wp-parsely-content-helper .wp-parsely-performance-panel div.multi-percentage-bar .bar-fill:first-child{border-radius:var(--radius) 0 0 var(--radius)}.wp-parsely-content-helper .wp-parsely-performance-panel div.multi-percentage-bar .bar-fill:last-child{border-radius:0 var(--radius) var(--radius) 0}.wp-parsely-content-helper .wp-parsely-performance-panel div.multi-percentage-bar .bar-fill.direct{background-color:hsl(var(--ref-direct))}.wp-parsely-content-helper .wp-parsely-performance-panel div.multi-percentage-bar .bar-fill.internal{background-color:hsl(var(--ref-internal))}.wp-parsely-content-helper .wp-parsely-performance-panel div.multi-percentage-bar .bar-fill.search{background-color:hsl(var(--ref-search))}.wp-parsely-content-helper .wp-parsely-performance-panel div.multi-percentage-bar .bar-fill.social{background-color:hsl(var(--ref-social))}.wp-parsely-content-helper .wp-parsely-performance-panel div.multi-percentage-bar .bar-fill.other{background-color:hsl(var(--ref-other))}.wp-parsely-content-helper .wp-parsely-performance-panel div.multi-percentage-bar:hover .bar-fill{filter:opacity(.5);transform:scaleX(1) scaleY(.7)}.wp-parsely-content-helper .wp-parsely-performance-panel .percentage-bar-labels{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:var(--grid-unit-20,1rem)}.wp-parsely-content-helper .wp-parsely-performance-panel .percentage-bar-labels .single-label{align-items:center;align-self:stretch;display:flex;gap:.5rem;justify-content:center}.wp-parsely-content-helper .wp-parsely-performance-panel .percentage-bar-labels .single-label .label-color{align-items:center;border-radius:.09375rem;display:flex;flex-direction:column;gap:.625rem;height:.625rem;justify-content:center;width:.1875rem}.wp-parsely-content-helper .wp-parsely-performance-panel .percentage-bar-labels .single-label .label-color.direct{background-color:hsl(var(--ref-direct))}.wp-parsely-content-helper .wp-parsely-performance-panel .percentage-bar-labels .single-label .label-color.internal{background-color:hsl(var(--ref-internal))}.wp-parsely-content-helper .wp-parsely-performance-panel .percentage-bar-labels .single-label .label-color.search{background-color:hsl(var(--ref-search))}.wp-parsely-content-helper .wp-parsely-performance-panel .percentage-bar-labels .single-label .label-color.social{background-color:hsl(var(--ref-social))}.wp-parsely-content-helper .wp-parsely-performance-panel .percentage-bar-labels .single-label .label-color.other{background-color:hsl(var(--ref-other))}.wp-parsely-content-helper .wp-parsely-performance-panel .percentage-bar-labels .single-label .label-text{-webkit-box-orient:vertical;-webkit-line-clamp:1;font-feature-settings:"ss06" on;color:var(--sidebar-black,#1e1e1e);display:-webkit-box;flex:1 0 0;font-size:var(--font-size--small,.75rem);font-style:normal;font-weight:400;line-height:1rem;overflow:hidden;text-overflow:ellipsis}.wp-parsely-content-helper .wp-parsely-performance-panel .percentage-bar-labels .single-label .label-value{color:var(--sidebar-black,#1e1e1e);font-size:var(--font-size--small,.75rem);font-style:normal;font-weight:590;line-height:1rem;text-align:right}.wp-parsely-content-helper .wp-parsely-performance-panel .referrers-list{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:var(--grid-unit-20,1rem);margin:1rem 0}.wp-parsely-content-helper .wp-parsely-performance-panel .referrers-list .referrers-row{align-items:center;align-self:stretch;display:flex;gap:var(--grid-unit-10,.5rem);justify-content:space-between}.wp-parsely-content-helper .wp-parsely-performance-panel .referrers-list .referrers-row .referrers-row-title{-webkit-box-orient:vertical;-webkit-line-clamp:1;font-feature-settings:"ss06" on;color:var(--sidebar-black,#1e1e1e);display:-webkit-box;flex:1;font-size:var(--font-size--small,.75rem);font-style:normal;font-weight:400;line-height:1rem;overflow:hidden;text-overflow:ellipsis}.wp-parsely-content-helper .wp-parsely-performance-panel .referrers-list .referrers-row .referrers-row-bar{display:flex;flex:1;flex-direction:column;gap:.625rem;justify-content:center;max-width:6.25rem}.wp-parsely-content-helper .wp-parsely-performance-panel .referrers-list .referrers-row .referrers-row-bar .percentage-bar{--radius:1.5px;background:var(--gray-400,#d7dbdf);border-radius:var(--radius);display:flex;height:.1875rem;margin:0;overflow:hidden}.wp-parsely-content-helper .wp-parsely-performance-panel .referrers-list .referrers-row .referrers-row-bar .percentage-bar:after{background:var(--blueberry,#3858e9);border-radius:var(--radius);content:"";height:100%;width:var(--bar-fill)}.wp-parsely-content-helper .wp-parsely-performance-panel .referrers-list .referrers-row .referrers-row-value{color:var(--sidebar-black,#1e1e1e);flex-shrink:0;font-size:var(--font-size--small,.75rem);font-style:normal;font-weight:590;line-height:1rem;min-width:3.125rem;text-align:right} +#wp-parsely-dashboard-widget,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"],.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-panel,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--parsely-green-10:#c7ecb1;--parsely-green-65:#2a691b;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--grid-unit-5:0.25rem;--grid-unit-10:0.5rem;--grid-unit-15:0.75rem;--grid-unit-20:1rem;--font-size--smaller:0.688rem;--font-size--small:0.75rem;--font-size--medium:0.875rem;--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e;--sidebar-white:#f0f0f0}.wp-parsely-related-posts{align-items:flex-start;display:flex;flex-direction:column}.wp-parsely-related-posts .related-posts-description{font-size:.8125rem;font-style:normal;font-weight:400;gap:.625rem;line-height:1.25rem}.wp-parsely-related-posts .related-posts-body,.wp-parsely-related-posts .related-posts-description{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;padding:.375rem 0 var(--grid-unit-20,1rem) 0}.wp-parsely-related-posts .related-posts-body{gap:1rem}.wp-parsely-related-posts .related-posts-body>div{width:100%}.wp-parsely-related-posts .related-posts-body .related-posts-settings{align-self:stretch;display:flex;flex-direction:column;gap:1rem;width:100%}.wp-parsely-related-posts .related-posts-body .related-posts-settings .components-base-control__field{margin-bottom:0}.wp-parsely-related-posts .related-posts-body .related-posts-filter-settings{display:flex;flex-direction:column;gap:var(--grid-unit-20,1rem);width:100%}.wp-parsely-related-posts .related-posts-body .related-posts-filter-settings .related-posts-filter-types{width:100%}.wp-parsely-related-posts .related-posts-body .related-posts-filter-settings .related-posts-filter-types .components-toggle-group-control{height:2.5rem;overflow:hidden}.wp-parsely-related-posts .related-posts-body .related-posts-filter-settings .related-posts-filter-types .components-toggle-group-control button{background:transparent;outline:2px solid transparent;outline-offset:-3px}.wp-parsely-related-posts .related-posts-body .related-posts-filter-settings .related-posts-filter-types .components-toggle-group-control button[data-active-item]{background:var(--sidebar-black,#1e1e1e);border-radius:3px;box-shadow:0 -3px 0 0 #fff,0 3px 0 0 #fff;transition:background 0s .5s,border-radius 0s .5s,box-shadow 0s .5s}.wp-parsely-related-posts .related-posts-body .related-posts-filter-settings .related-posts-filter-values{width:100%}.wp-parsely-related-posts .related-posts-body .related-posts-filter-settings .related-posts-filter-values .components-combobox-control__suggestions-container .components-flex{height:2.25rem}.wp-parsely-related-posts .related-posts-body .related-posts-filter-settings .related-posts-filter-values .components-combobox-control__suggestions-container .components-flex input{margin:0 var(--grid-unit-15,.75rem)}.wp-parsely-related-posts .related-posts-body .related-posts-wrapper .related-posts-descr{font-size:.8125rem;font-style:normal;font-weight:400;line-height:1.25rem}.wp-parsely-related-posts .related-posts-body .related-posts-wrapper .related-posts-empty,.wp-parsely-related-posts .related-posts-body .related-posts-wrapper .related-posts-loading-message{color:var(--gray-900,#1e1e1e);font-size:.75rem;font-style:normal;font-weight:700;line-height:var(--grid-unit-20,1rem);overflow:hidden;text-overflow:ellipsis}.wp-parsely-related-posts .related-posts-body .related-posts-list{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:var(--grid-unit-20,1rem);padding:.375rem 0 var(--grid-unit-20,1rem) 0}.wp-parsely-related-posts .related-posts-body .related-posts-list .related-post-single{align-items:flex-start;align-self:stretch;border:1px solid var(--Gutenberg-Gray-400,#ccc);border-radius:2px;display:flex;flex-direction:column}.wp-parsely-related-posts .related-posts-body .related-posts-list .related-post-single .related-post-title{align-items:center;align-self:stretch;display:flex;flex-direction:column;font-size:.875rem;font-style:normal;font-weight:600;gap:.5rem;justify-content:center;line-height:1.25rem;overflow:hidden;padding:var(--grid-unit-20,1rem);text-decoration-line:underline;text-overflow:ellipsis}.wp-parsely-related-posts .related-posts-body .related-posts-list .related-post-single .related-post-actions{align-items:center;align-self:stretch;border-top:1px solid var(--gray-400,#ccc);display:flex;flex-wrap:wrap;gap:var(--grid-unit-20,1rem);padding:0 var(--grid-unit-10,.5rem)}.wp-parsely-related-posts .related-posts-body .related-posts-list .related-post-single .related-post-actions .related-post-info{align-items:center;display:flex;flex-grow:1;gap:var(--grid-unit-10,.5rem)}.wp-parsely-related-posts .related-posts-body .related-posts-list .related-post-single .related-post-actions .related-post-info>div:first-child{display:flex;flex:1;gap:var(--grid-unit-10,.5rem)}.wp-parsely-related-posts .related-posts-body .related-posts-list .related-post-single .related-post-actions .related-post-info>div:last-child{display:flex;gap:var(--grid-unit-10,.5rem)}.wp-parsely-related-posts .related-posts-body .related-posts-list .related-post-single .related-post-actions .related-post-info .related-post-metric{align-items:center;display:flex}.wp-parsely-related-posts .related-posts-body .related-posts-list .related-post-single .related-post-actions .related-post-info .related-post-metric .parsely-post-metric-data{align-items:center;display:flex;gap:var(--grid-unit-5,.25rem)}.wp-parsely-related-posts .related-posts-body .related-posts-list .related-post-single .related-post-actions .related-post-info .related-post-linked{fill:#008a20;align-items:center;display:flex;margin-left:auto}.wp-parsely-related-posts .related-posts-body .related-posts-list .related-post-single .related-post-actions .related-post-info .wp-parsely-icon path{fill:#1e1e1e}.wp-parsely-related-posts .related-posts-body .related-posts-list .related-post-single .related-post-actions .related-post-info .wp-parsely-icon:hover path{fill:#0073aa} +#wp-parsely-dashboard-widget,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"],.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-panel,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--parsely-green-10:#c7ecb1;--parsely-green-65:#2a691b;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--grid-unit-5:0.25rem;--grid-unit-10:0.5rem;--grid-unit-15:0.75rem;--grid-unit-20:1rem;--font-size--smaller:0.688rem;--font-size--small:0.75rem;--font-size--medium:0.875rem;--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e;--sidebar-white:#f0f0f0}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper{display:flex;flex-direction:column}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .title-suggestions-settings{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:var(--grid-unit-20,1rem);padding:.375rem 0 var(--grid-unit-20,1rem) 0}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .title-suggestions-settings>div{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:var(--grid-unit-10,.5rem)}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .title-suggestions-header{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:.625rem;padding-bottom:var(--grid-unit-20,1rem)}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .title-suggestions-header .parsely-write-titles-text strong{text-transform:lowercase}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .title-suggestions-generate{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;padding-top:.375rem}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .title-suggestions-generate .components-button{align-items:center;align-self:stretch;border-radius:2px;display:flex;height:2.5rem;justify-content:center;padding:var(--grid-unit-10,.5rem) var(--grid-unit-15,.75rem)}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .wp-parsely-dropdown-label{align-self:stretch;color:var(--sidebar-black,#1e1e1e);font-size:var(--font-size-smaller,.6875rem);font-style:normal;font-weight:600;line-height:var(--grid-unit-20,1rem);text-transform:uppercase}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .parsely-write-titles-accepted-title-container .parsely-write-titles-accepted-title{font-size:1rem;font-weight:600;line-height:1.25rem;margin:0 0 .9375rem;text-align:center}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .parsely-write-titles-accepted-title-container .parsely-write-titles-accepted-title-actions{display:flex;gap:.625rem;justify-content:center;margin:.625rem 0}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .wp-parsely-title-suggestion{align-items:flex-start;align-self:stretch;border:1px solid var(--Gutenberg-Gray-400,#ccc);border-radius:2px;display:flex;flex-direction:column;gap:var(--grid-unit-10,.5rem)}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .wp-parsely-title-suggestion.pinned-title{background:var(--Gutenberg-Gray-100,#f0f0f0)}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .wp-parsely-title-suggestion .suggested-title{align-items:center;align-self:stretch;color:#1e1e1e;display:flex;flex-direction:column;font-size:.75rem;font-style:normal;font-weight:600;gap:var(--grid-unit-10,.5rem);justify-content:center;line-height:1.25rem;padding:var(--grid-unit-15,.75rem) var(--grid-unit-15,.75rem) 0 var(--grid-unit-15,.75rem)}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .wp-parsely-title-suggestion .suggested-title .suggested-title-original{align-self:flex-start;margin:0}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .wp-parsely-title-suggestion .suggested-title-actions{align-items:center;align-self:stretch;border-top:1px solid var(--Gutenberg-Gray-400,#ccc);display:flex;flex-wrap:wrap;gap:var(--grid-unit-20,1rem);height:2.5rem;padding:0 var(--grid-unit-10,.5rem)}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .wp-parsely-title-suggestion .suggested-title-actions .suggested-title-actions-container{align-items:center;display:flex;flex-grow:1;gap:var(--grid-unit-10,.5rem)}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .wp-parsely-title-suggestion .suggested-title-actions .suggested-title-actions-container .suggested-title-actions-left,.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .wp-parsely-title-suggestion .suggested-title-actions .suggested-title-actions-container .suggested-title-actions-right{display:flex;gap:var(--grid-unit-10,.5rem)}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .title-suggestions-container{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:var(--grid-unit-20,1rem);margin-bottom:var(--grid-unit-20,1rem);position:relative}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .title-suggestions-container .wp-parsely-loading-overlay{align-items:center;background-color:hsla(0,0%,100%,.9);bottom:0;display:flex;flex-direction:column;gap:var(--grid-unit-10,.5rem);justify-content:center;left:0;position:absolute;right:0;top:0;z-index:1}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .title-suggestions-container .wp-parsely-loading-overlay .components-spinner{transform:scale(1.125)}.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper .wp-parsely-pinned-suggestions{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column}.wp-parsely-popover .components-popover__content{width:15.5rem}.wp-parsely-suggested-title-modal{align-items:flex-start;display:flex;flex-direction:column;width:20rem}.wp-parsely-suggested-title-modal h2{color:var(--sidebar-black,#1e1e1e);font-size:1rem;font-style:normal;font-weight:600;line-height:1.5rem}.wp-parsely-suggested-title-modal .suggested-title-modal-actions{align-items:center;align-self:stretch;display:flex;gap:var(--grid-unit-15,.75rem);justify-content:flex-end;margin-top:1.5rem} +#wp-parsely-dashboard-widget,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"],.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-panel,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--parsely-green-10:#c7ecb1;--parsely-green-65:#2a691b;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--grid-unit-5:0.25rem;--grid-unit-10:0.5rem;--grid-unit-15:0.75rem;--grid-unit-20:1rem;--font-size--smaller:0.688rem;--font-size--small:0.75rem;--font-size--medium:0.875rem;--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e;--sidebar-white:#f0f0f0}.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"]:focus,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"]:hover{background-color:#fff}.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"].is-pressed,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"].is-pressed:hover{background-color:var(--parsely-green-65)}.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"].is-pressed .wp-parsely-sidebar-icon path,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"].is-pressed:hover .wp-parsely-sidebar-icon path{fill:var(--parsely-green-10)}.wp-parsely-content-helper .wp-parsely-sidebar-header{align-items:flex-start;align-self:stretch;display:flex;flex-direction:column;gap:1.5rem}.wp-parsely-content-helper .wp-parsely-sidebar-header .components-button{align-items:center;display:flex;justify-content:center;width:100%}.wp-parsely-content-helper .wp-parsely-sidebar-main-panel .wp-parsely-sidebar-tabs .components-tab-panel__tabs button{align-items:center;display:flex;flex:1 0 0;flex-direction:column;height:3rem}.wp-parsely-content-helper .wp-parsely-sidebar-main-panel .wp-parsely-sidebar-tabs .components-tab-panel__tabs .components-tab-panel__tabs-item:after{background:var(--gray-400);height:calc(var(--wp-admin-border-width-focus)*1);outline:2px solid transparent;outline-offset:-1px}.wp-parsely-content-helper .wp-parsely-sidebar-main-panel .wp-parsely-sidebar-tabs .components-tab-panel__tabs .components-tab-panel__tabs-item.is-active:after{background:var(--wp-components-color-accent,var(--wp-admin-theme-color,#3858e9))}.wp-parsely-content-helper .parsely-spinner-wrapper{display:flex;justify-content:center;margin:2.5rem 0}.wp-parsely-content-helper .parsely-spinner-wrapper svg{height:22px;width:22px}.wp-parsely-content-helper .content-helper-error-message,.wp-parsely-content-helper .wp-parsely-content-helper-error .content-helper-error-message{margin-top:.9375rem!important}.wp-parsely-content-helper p.content-helper-error-message-hint{color:var(--gray-700)} diff --git a/build/content-helper/editor-sidebar.js b/build/content-helper/editor-sidebar.js index e294dcfde..892b5cb46 100644 --- a/build/content-helper/editor-sidebar.js +++ b/build/content-helper/editor-sidebar.js @@ -1,16 +1,24 @@ -!function(){"use strict";var e={251:function(e,t,n){var r=n(196),i=Symbol.for("react.element"),s=Symbol.for("react.fragment"),a=Object.prototype.hasOwnProperty,o=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};function c(e,t,n){var r,s={},c=null,u=null;for(r in void 0!==n&&(c=""+n),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(u=t.ref),t)a.call(t,r)&&!l.hasOwnProperty(r)&&(s[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps)void 0===s[r]&&(s[r]=t[r]);return{$$typeof:i,type:e,key:c,ref:u,props:s,_owner:o.current}}t.Fragment=s,t.jsx=c,t.jsxs=c},893:function(e,t,n){e.exports=n(251)},196:function(e){e.exports=window.React}},t={};function n(r){var i=t[r];if(void 0!==i)return i.exports;var s=t[r]={exports:{}};return e[r](s,s.exports,n),s.exports}n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e,t,r,i=n(893),s=window.wp.components,a=window.wp.coreData,o=window.wp.data,l=window.wp.editPost,c=window.wp.editor,u=window.wp.element,p=window.wp.i18n,d=window.wp.plugins,h=function(){function e(){this._tkq=[],this.isLoaded=!1,this.isEnabled=!1,"undefined"!=typeof wpParselyTracksTelemetry&&(this.isEnabled=!0,this.loadTrackingLibrary())}return e.getInstance=function(){return window.wpParselyTelemetryInstance||Object.defineProperty(window,"wpParselyTelemetryInstance",{value:new e,writable:!1,configurable:!1,enumerable:!1}),window.wpParselyTelemetryInstance},e.prototype.loadTrackingLibrary=function(){var e=this,t=document.createElement("script");t.async=!0,t.src="//stats.wp.com/w.js",t.onload=function(){e.isLoaded=!0,e._tkq=window._tkq||[]},document.head.appendChild(t)},e.trackEvent=function(t,n){return void 0===n&&(n={}),r=this,i=void 0,a=function(){var r;return function(e,t){var n,r,i,s,a={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return s={next:o(0),throw:o(1),return:o(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function o(o){return function(l){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;s&&(s=0,o[0]&&(a=0)),a;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return a.label++,{value:o[1],done:!1};case 5:a.label++,r=o[1],o=[0];continue;case 7:o=a.ops.pop(),a.trys.pop();continue;default:if(!((i=(i=a.trys).length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]=1e4&&(clearInterval(s),n("Telemetry library not loaded"))}),100);else n("Telemetry not enabled")}))},e.prototype.trackEvent=function(t,n){var r;this.isLoaded?(0!==t.indexOf(e.TRACKS_PREFIX)&&(t=e.TRACKS_PREFIX+t),this.isEventNameValid(t)?(n=this.prepareProperties(n),null===(r=this._tkq)||void 0===r||r.push(["recordEvent",t,n])):console.error("Error tracking event: Invalid event name")):console.error("Error tracking event: Telemetry not loaded")},e.prototype.isTelemetryEnabled=function(){return this.isEnabled},e.prototype.isProprietyValid=function(t){return e.PROPERTY_REGEX.test(t)},e.prototype.isEventNameValid=function(t){return e.EVENT_NAME_REGEX.test(t)},e.prototype.prepareProperties=function(e){return(e=this.sanitizeProperties(e)).parsely_version=wpParselyTracksTelemetry.version,wpParselyTracksTelemetry.user&&(e._ut=wpParselyTracksTelemetry.user.type,e._ui=wpParselyTracksTelemetry.user.id),wpParselyTracksTelemetry.vipgo_env&&(e.vipgo_env=wpParselyTracksTelemetry.vipgo_env),this.sanitizeProperties(e)},e.prototype.sanitizeProperties=function(e){var t=this,n={};return Object.keys(e).forEach((function(r){t.isProprietyValid(r)&&(n[r]=e[r])})),n},e.TRACKS_PREFIX="wpparsely_",e.EVENT_NAME_REGEX=/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/,e.PROPERTY_REGEX=/^[a-z_][a-z0-9_]*$/,e}(),f=(h.trackEvent,function(e){var t=e.text,n=void 0===t?(0,p.__)("Beta","wp-parsely"):t,r=e.color,s=void 0===r?"var(--parsely-green)":r,a=e.fontSize,o={backgroundColor:s,fontSize:void 0===a?"0.75rem":a};return(0,i.jsx)("div",{className:"wp-parsely-beta-badge",style:o,children:n})}),y=(0,u.forwardRef)((function({icon:e,size:t=24,...n},r){return(0,u.cloneElement)(e,{width:t,height:t,...n,ref:r})})),g=n(196),v=window.wp.primitives,w=(0,g.createElement)(v.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,g.createElement)(v.Path,{d:"M10.6 6L9.4 7l4.6 5-4.6 5 1.2 1 5.4-6z"})),m=function(){return(0,i.jsxs)(s.SVG,{"aria-hidden":"true",width:"24",height:"24",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",xmlnsXlink:"http://www.w3.org/1999/xlink",children:[(0,i.jsx)(s.G,{transform:"matrix(1, 0, 0, 1, 1.7763568394002505e-15, 0)"}),(0,i.jsx)(s.Path,{d:"M 16.693 6.167 A 4.541 4.541 0 0 1 12.152 10.708 A 4.541 4.541 0 0 1 7.611 6.167 A 4.541 4.541 0 0 1 12.152 1.626 A 4.541 4.541 0 0 1 16.693 6.167 Z",style:{fill:"rgba(216, 216, 216, 0)",fillOpacity:0,strokeWidth:"2.5px",stroke:"rgb(0, 0, 0)"}}),(0,i.jsx)(s.Path,{d:"M 3.016 23.76 L 3.0135 22.4385 C 3.0109999999999997 21.117 3.0060000000000002 18.474 3.3204999999999996 16.911 C 3.635 15.347999999999999 4.268999999999999 14.865 7.068666666666666 14.6235 C 9.868333333333332 14.382 14.833666666666666 14.382 17.585833333333333 14.658 C 20.337999999999997 14.934 20.877 15.485999999999999 21.1465 17.0435 C 21.416 18.601 21.416 21.164 21.416 22.4455 L 21.416 23.727",style:{fillOpacity:0,fill:"rgb(255, 255, 255)",strokeWidth:"2.5px",stroke:"rgb(0, 0, 0)"}})]})},_={journalist:{label:(0,p.__)("Journalist","wp-parsely"),emoji:"📰"},editorialWriter:{label:(0,p.__)("Editorial Writer","wp-parsely"),emoji:"✍️"},investigativeReporter:{label:(0,p.__)("Investigative Reporter","wp-parsely"),emoji:"🕵️"},techAnalyst:{label:(0,p.__)("Tech Analyst","wp-parsely"),emoji:"💻"},businessAnalyst:{label:(0,p.__)("Business Analyst","wp-parsely"),emoji:"📈"},culturalCommentator:{label:(0,p.__)("Cultural Commentator","wp-parsely"),emoji:"🌍"},scienceCorrespondent:{label:(0,p.__)("Science Correspondent","wp-parsely"),emoji:"🔬"},politicalAnalyst:{label:(0,p.__)("Political Analyst","wp-parsely"),emoji:"🏛️"},healthWellnessAdvocate:{label:(0,p.__)("Health and Wellness Advocate","wp-parsely"),emoji:"🍏"},environmentalJournalist:{label:(0,p.__)("Environmental Journalist","wp-parsely"),emoji:"🌳"},custom:{label:(0,p.__)("Use a custom persona","wp-parsely"),emoji:"🔧"}},b=Object.keys(_),x=function(e){return"custom"===e||""===e?_.custom.label:j(e)?e:_[e].label},j=function(e){return!b.includes(e)||"custom"===e},P=function(e){var t=e.value,n=e.onChange,r=(0,u.useState)(""),a=r[0],o=r[1];return(0,i.jsx)("div",{className:"parsely-persona-selector-custom",children:(0,i.jsx)(s.TextControl,{value:a||t,onChange:function(e){n(e),o(e)},help:(0,p.__)("Enter a custom persona","wp-parsely")})})},T=function(e){var t=e.persona,n=e.label,r=void 0===n?(0,p.__)("Select a persona","wp-parsely"):n,a=e.onChange,o=e.onDropdownChange,l=e.disabled,c=void 0!==l&&l,u=e.allowCustom,d=void 0!==u&&u;return(0,i.jsxs)(s.Disabled,{isDisabled:c,children:[(0,i.jsx)(s.DropdownMenu,{label:(0,p.__)("Persona","wp-parsely"),icon:m,className:"parsely-persona-selector-dropdown"+(c?" is-disabled":""),popoverProps:{className:"wp-parsely-popover"},toggleProps:{children:(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("div",{className:"parsely-persona-selector-label",children:r}),(0,i.jsx)(y,{icon:w})]})},children:function(e){var n=e.onClose;return(0,i.jsx)(s.MenuGroup,{label:(0,p.__)("Select a persona","wp-parsely"),children:(0,i.jsx)(i.Fragment,{children:b.map((function(e){if(!d&&"custom"===e)return null;var r=_[e];return(0,i.jsxs)(s.MenuItem,{isSelected:e===t,className:e===t?"is-selected":"",role:"menuitemradio",onClick:function(){null==o||o(e),a(e),n()},children:[r.emoji," ",r.label]},e)}))})})}}),d&&j(t)&&(0,i.jsx)(P,{onChange:function(e){a(""!==e?e:"custom")},value:"custom"===t?"":t})]})},S=(0,g.createElement)(v.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,g.createElement)(v.Path,{fillRule:"evenodd",d:"M6.863 13.644L5 13.25h-.5a.5.5 0 01-.5-.5v-3a.5.5 0 01.5-.5H5L18 6.5h2V16h-2l-3.854-.815.026.008a3.75 3.75 0 01-7.31-1.549zm1.477.313a2.251 2.251 0 004.356.921l-4.356-.921zm-2.84-3.28L18.157 8h.343v6.5h-.343L5.5 11.823v-1.146z",clipRule:"evenodd"})),E={neutral:{label:(0,p.__)("Neutral","wp-parsely"),emoji:"😐"},formal:{label:(0,p.__)("Formal","wp-parsely"),emoji:"🎩"},humorous:{label:(0,p.__)("Humorous","wp-parsely"),emoji:"😂"},confident:{label:(0,p.__)("Confident","wp-parsely"),emoji:"😎"},provocative:{label:(0,p.__)("Provocative","wp-parsely"),emoji:"😈"},serious:{label:(0,p.__)("Serious","wp-parsely"),emoji:"🧐"},inspirational:{label:(0,p.__)("Inspirational","wp-parsely"),emoji:"✨"},skeptical:{label:(0,p.__)("Skeptical","wp-parsely"),emoji:"🤨"},conversational:{label:(0,p.__)("Conversational","wp-parsely"),emoji:"💬"},analytical:{label:(0,p.__)("Analytical","wp-parsely"),emoji:"🤓"},custom:{label:(0,p.__)("Use a custom tone","wp-parsely"),emoji:"🔧"}},C=Object.keys(E),N=function(e){return"custom"===e||""===e?E.custom.label:k(e)?e:E[e].label},k=function(e){return!C.includes(e)||"custom"===e},O=function(e){var t=e.value,n=e.onChange,r=(0,u.useState)(""),a=r[0],o=r[1];return(0,i.jsx)("div",{className:"parsely-tone-selector-custom",children:(0,i.jsx)(s.TextControl,{value:a||t,onChange:function(e){n(e),o(e)},help:(0,p.__)("Enter a custom tone","wp-parsely")})})},A=function(e){var t=e.tone,n=e.label,r=void 0===n?(0,p.__)("Select a tone","wp-parsely"):n,a=e.onChange,o=e.onDropdownChange,l=e.disabled,c=void 0!==l&&l,u=e.allowCustom,d=void 0!==u&&u;return(0,i.jsxs)(s.Disabled,{isDisabled:c,children:[(0,i.jsx)(s.DropdownMenu,{label:(0,p.__)("Tone","wp-parsely"),icon:S,className:"parsely-tone-selector-dropdown"+(c?" is-disabled":""),popoverProps:{className:"wp-parsely-popover"},toggleProps:{children:(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("div",{className:"parsely-tone-selector-label",children:r}),(0,i.jsx)(y,{icon:w})]})},children:function(e){var n=e.onClose;return(0,i.jsx)(s.MenuGroup,{label:(0,p.__)("Select a tone","wp-parsely"),children:(0,i.jsx)(i.Fragment,{children:C.map((function(e){if(!d&&"custom"===e)return null;var r=E[e];return(0,i.jsxs)(s.MenuItem,{isSelected:e===t,className:e===t?"is-selected":"",role:"menuitemradio",onClick:function(){null==o||o(e),a(e),n()},children:[r.emoji," ",r.label]},e)}))})})}}),d&&k(t)&&(0,i.jsx)(O,{onChange:function(e){a(""!==e?e:"custom")},value:"custom"===t?"":t})]})},R=window.wp.apiFetch,L=n.n(R),I=function(e){var t=e.size,n=void 0===t?24:t;return(0,i.jsxs)(s.SVG,{height:n,viewBox:"0 0 60 65",width:n,xmlns:"http://www.w3.org/2000/svg",children:[(0,i.jsx)(s.Path,{fill:"#5ba745",d:"M23.72,51.53c0-.18,0-.34-.06-.52a13.11,13.11,0,0,0-2.1-5.53A14.74,14.74,0,0,0,19.12,43c-.27-.21-.5-.11-.51.22l-.24,3.42c0,.33-.38.35-.49,0l-1.5-4.8a1.4,1.4,0,0,0-.77-.78,23.91,23.91,0,0,0-3.1-.84c-1.38-.24-3.39-.39-3.39-.39-.34,0-.45.21-.25.49l2.06,3.76c.2.27,0,.54-.29.33l-4.51-3.6a3.68,3.68,0,0,0-2.86-.48c-1,.16-2.44.46-2.44.46a.68.68,0,0,0-.39.25.73.73,0,0,0-.14.45S.41,43,.54,44a3.63,3.63,0,0,0,1.25,2.62L6.48,50c.28.2.09.49-.23.37l-4.18-.94c-.32-.12-.5,0-.4.37,0,0,.69,1.89,1.31,3.16a24,24,0,0,0,1.66,2.74,1.34,1.34,0,0,0,1,.52l5,.13c.33,0,.41.38.1.48L7.51,58c-.31.1-.34.35-.07.55a14.29,14.29,0,0,0,3.05,1.66,13.09,13.09,0,0,0,5.9.5,25.13,25.13,0,0,0,4.34-1,9.55,9.55,0,0,1-.08-1.2,9.32,9.32,0,0,1,3.07-6.91"}),(0,i.jsx)(s.Path,{fill:"#5ba745",d:"M59.7,41.53a.73.73,0,0,0-.14-.45.68.68,0,0,0-.39-.25s-1.43-.3-2.44-.46a3.64,3.64,0,0,0-2.86.48l-4.51,3.6c-.26.21-.49-.06-.29-.33l2.06-3.76c.2-.28.09-.49-.25-.49,0,0-2,.15-3.39.39a23.91,23.91,0,0,0-3.1.84,1.4,1.4,0,0,0-.77.78l-1.5,4.8c-.11.32-.48.3-.49,0l-.24-3.42c0-.33-.24-.43-.51-.22a14.74,14.74,0,0,0-2.44,2.47A13.11,13.11,0,0,0,36.34,51c0,.18,0,.34-.06.52a9.26,9.26,0,0,1,3,8.1,24.1,24.1,0,0,0,4.34,1,13.09,13.09,0,0,0,5.9-.5,14.29,14.29,0,0,0,3.05-1.66c.27-.2.24-.45-.07-.55l-3.22-1.17c-.31-.1-.23-.47.1-.48l5-.13a1.38,1.38,0,0,0,1-.52A24.6,24.6,0,0,0,57,52.92c.61-1.27,1.31-3.16,1.31-3.16.1-.33-.08-.49-.4-.37l-4.18.94c-.32.12-.51-.17-.23-.37l4.69-3.34A3.63,3.63,0,0,0,59.46,44c.13-1,.24-2.47.24-2.47"}),(0,i.jsx)(s.Path,{fill:"#5ba745",d:"M46.5,25.61c0-.53-.35-.72-.8-.43l-4.86,2.66c-.45.28-.56-.27-.23-.69l4.66-6.23a2,2,0,0,0,.28-1.68,36.51,36.51,0,0,0-2.19-4.89,34,34,0,0,0-2.81-3.94c-.33-.41-.74-.35-.91.16l-2.28,5.68c-.16.5-.6.48-.59-.05l.28-8.93a2.54,2.54,0,0,0-.66-1.64S35,4.27,33.88,3.27,30.78.69,30.78.69a1.29,1.29,0,0,0-1.54,0s-1.88,1.49-3.12,2.59-2.48,2.35-2.48,2.35A2.5,2.5,0,0,0,23,7.27l.27,8.93c0,.53-.41.55-.58.05l-2.29-5.69c-.17-.5-.57-.56-.91-.14a35.77,35.77,0,0,0-3,4.2,35.55,35.55,0,0,0-2,4.62,2,2,0,0,0,.27,1.67l4.67,6.24c.33.42.23,1-.22.69l-4.87-2.66c-.45-.29-.82-.1-.82.43a18.6,18.6,0,0,0,.83,5.07,20.16,20.16,0,0,0,5.37,7.77c3.19,3,5.93,7.8,7.45,11.08A9.6,9.6,0,0,1,30,49.09a9.31,9.31,0,0,1,2.86.45c1.52-3.28,4.26-8.11,7.44-11.09a20.46,20.46,0,0,0,5.09-7,19,19,0,0,0,1.11-5.82"}),(0,i.jsx)(s.Path,{fill:"#5ba745",d:"M36.12,58.44A6.12,6.12,0,1,1,30,52.32a6.11,6.11,0,0,1,6.12,6.12"})]})};!function(e){e.Minutes10="10m",e.Hour="1h",e.Hours2="2h",e.Hours4="4h",e.Hours24="24h",e.Days7="7d",e.Days30="30d"}(e||(e={})),function(e){e.Views="views",e.AvgEngaged="avg_engaged"}(t||(t={})),function(e){e.Author="author",e.Section="section",e.Tag="tag",e.Unavailable="unavailable"}(r||(r={}));var F=function(e,t){return Object.values(t).includes(e)};function D(e,t){void 0===t&&(t=!1);var n=parseInt(e,10),r=e.charAt(e.length-1),i=(0,p.__)("Unknown Period","wp-parsely");switch(r){case"m":if(1===n){i=(0,p.__)("Last Minute","wp-parsely");break}i=(0,p.sprintf)(/* translators: 1: Number of minutes */(0,p._n)("Last %1$d Minute","Last %1$d Minutes",n,"wp-parsely"),n);break;case"h":if(1===n){i=(0,p.__)("Last Hour","wp-parsely");break}i=(0,p.sprintf)(/* translators: 1: Number of hours */(0,p._n)("Last %1$d Hour","Last %1$d Hours",n,"wp-parsely"),n);break;case"d":if(1===n){i=(0,p.__)("Last Day","wp-parsely");break}i=(0,p.sprintf)(/* translators: 1: Number of days */(0,p._n)("Last %1$d Day","Last %1$d Days",n,"wp-parsely"),n)}return t?i.toLocaleLowerCase():i}function M(e){switch(e){case t.Views:return(0,p.__)("Page Views","wp-parsely");case t.AvgEngaged:return(0,p.__)("Avg. Time","wp-parsely");default:return(0,p.__)("Unknown Metric","wp-parsely")}}var V=function(e){void 0===e&&(e=null);var t="";(null==e?void 0:e.children)&&(t=e.children);var n="content-helper-error-message";return(null==e?void 0:e.className)&&(n+=" "+e.className),(0,i.jsx)("div",{className:n,"data-testid":null==e?void 0:e.testId,dangerouslySetInnerHTML:{__html:t}})},z=function(e){return void 0===e&&(e=null),(0,i.jsx)(V,{className:null==e?void 0:e.className,testId:"empty-credentials-message",children:window.wpParselyEmptyCredentialsMessage})},B=function(){return B=Object.assign||function(e){for(var t,n=1,r=arguments.length;n=c){var u=t;(i=r/c)%1>1/o&&(u=i>10?1:2),u=parseFloat(i.toFixed(2))===parseFloat(i.toFixed(0))?0:u,s=i.toFixed(u),a=l}o=c})),s+n+a}var U,$,W=window.wp.url,q=(U=function(e,t){return U=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},U(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function __(){this.constructor=e}U(e,t),e.prototype=null===t?Object.create(t):(__.prototype=t.prototype,new __)});!function(e){e.CannotFormulateApiQuery="ch_cannot_formulate_api_query",e.FetchError="fetch_error",e.HttpRequestFailed="http_request_failed",e[e.ParselyApiForbidden=403]="ParselyApiForbidden",e.ParselyApiResponseContainsError="ch_response_contains_error",e.ParselyApiReturnedNoData="ch_parsely_api_returned_no_data",e.ParselyApiReturnedTooManyResults="ch_parsely_api_returned_too_many_results",e[e.ParselyApiUnauthorized=401]="ParselyApiUnauthorized",e.PluginCredentialsNotSetMessageDetected="parsely_credentials_not_set_message_detected",e.PluginSettingsApiSecretNotSet="parsely_api_secret_not_set",e.PluginSettingsSiteIdNotSet="parsely_site_id_not_set",e.PostIsNotPublished="ch_post_not_published"}($||($={}));var Q=function(e){function t(n,r,i){void 0===i&&(i=(0,p.__)("Error: ","wp-parsely"));var s=e.call(this,i+n)||this;s.hint=null,s.name=s.constructor.name,s.code=r;var a=[$.ParselyApiForbidden,$.ParselyApiResponseContainsError,$.ParselyApiReturnedNoData,$.ParselyApiReturnedTooManyResults,$.ParselyApiUnauthorized,$.PluginCredentialsNotSetMessageDetected,$.PluginSettingsApiSecretNotSet,$.PluginSettingsSiteIdNotSet,$.PostIsNotPublished];return s.retryFetch=!a.includes(s.code),Object.setPrototypeOf(s,t.prototype),s}return q(t,e),t.prototype.Message=function(e){return void 0===e&&(e=null),[$.PluginCredentialsNotSetMessageDetected,$.PluginSettingsSiteIdNotSet,$.PluginSettingsApiSecretNotSet].includes(this.code)?z(e):(this.code===$.FetchError&&(this.hint=this.Hint((0,p.__)("This error can sometimes be caused by ad-blockers or browser tracking protections. Please add this site to any applicable allow lists and try again.","wp-parsely"))),this.code===$.ParselyApiForbidden&&(this.hint=this.Hint((0,p.__)("Please ensure that the Site ID and API Secret given in the plugin's settings are correct.","wp-parsely"))),this.code===$.HttpRequestFailed&&(this.hint=this.Hint((0,p.__)("The Parse.ly API cannot be reached. Please verify that you are online.","wp-parsely"))),this.code===$.ParselyApiUnauthorized&&(this.message=(0,p.__)("This feature is accessible to select customers participating in its beta testing.","wp-parsely")),(0,i.jsx)(V,{className:null==e?void 0:e.className,testId:"error",children:"

".concat(this.message,"

").concat(this.hint?this.hint:"")}))},t.prototype.Hint=function(e){return'

'.concat((0,p.__)("Hint:","wp-parsely")," ").concat(e,"

")},t}(Error),X={month:"short",day:"numeric",year:"numeric"},Z={month:"short",day:"numeric"},Y=(0,p.__)("Date N/A","wp-parsely");function J(e){if(!1===function(e){return!isNaN(+e)&&0!==e.getTime()}(e))return Y;var t=X;return e.getUTCFullYear()===(new Date).getUTCFullYear()&&(t=Z),Intl.DateTimeFormat(document.documentElement.lang||"en",t).format(e)}function K(e){return{period_start:e,period_end:""}}var ee=function(){return ee=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]1?[2,Promise.reject(new Q((0,p.sprintf)(/* translators: URL of the published post */ -(0,p.__)("Multiple results were returned for the post %s by the Parse.ly API.","wp-parsely"),t),$.ParselyApiReturnedTooManyResults))]:[2,n.data[0]]}}))}))},e.prototype.fetchReferrerDataFromWpEndpoint=function(e,t,n){return te(this,void 0,void 0,(function(){var r,i;return ne(this,(function(s){switch(s.label){case 0:return s.trys.push([0,2,,3]),[4,L()({path:(0,W.addQueryArgs)("/wp-parsely/v1/referrers/post/detail",ee(ee({},K(e)),{itm_source:this.itmSource,total_views:n,url:t}))})];case 1:return r=s.sent(),[3,3];case 2:return i=s.sent(),[2,Promise.reject(new Q(i.message,i.code))];case 3:return(null==r?void 0:r.error)?[2,Promise.reject(new Q(r.error.message,$.ParselyApiResponseContainsError))]:[2,r.data]}}))}))},e}(),ie=function(){return ie=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]0&&e.retryFetch?[4,new Promise((function(e){return setTimeout(e,500)}))]:[3,3];case 1:return t.sent(),[4,r(i-1)];case 2:return t.sent(),[3,4];case 3:p(e),o(!1),t.label=4;case 4:return[2]}}))}))})),[2]}))}))};return o(!0),r(1),function(){p(void 0)}}),[n]),c?c.Message():a?(0,i.jsx)("div",{className:"parsely-spinner-wrapper","data-testid":"parsely-spinner-wrapper",children:(0,i.jsx)(s.Spinner,{})}):(0,i.jsx)(le,{data:h,period:n})}function le(e){return(0,i.jsxs)("div",{className:"performance-details-panel",children:[(0,i.jsx)("div",{className:"section period",children:D(e.period)}),(0,i.jsx)(ce,ie({},e)),(0,i.jsx)(ue,ie({},e)),(0,i.jsx)(pe,ie({},e)),(0,i.jsx)(de,ie({},e))]})}function ce(e){var t=e.data;return(0,i.jsx)("div",{className:"section general-performance",children:(0,i.jsxs)("table",{children:[(0,i.jsx)("tbody",{children:(0,i.jsxs)("tr",{children:[(0,i.jsx)("td",{children:H(t.views)}),(0,i.jsx)("td",{children:H(t.visitors)}),(0,i.jsx)("td",{children:t.avgEngaged})]})}),(0,i.jsx)("tfoot",{children:(0,i.jsxs)("tr",{children:[(0,i.jsx)("th",{children:(0,p.__)("Page Views","wp-parsely")}),(0,i.jsx)("th",{children:(0,p.__)("Visitors","wp-parsely")}),(0,i.jsx)("th",{children:(0,p.__)("Avg. Time","wp-parsely")})]})})]})})}function ue(e){var t=e.data;delete t.referrers.types.totals;var n=function(e){switch(e){case"social":return(0,p.__)("Social","wp-parsely");case"search":return(0,p.__)("Search","wp-parsely");case"other":return(0,p.__)("Other","wp-parsely");case"internal":return(0,p.__)("Internal","wp-parsely");case"direct":return(0,p.__)("Direct","wp-parsely")}return e};return(0,i.jsxs)("div",{className:"section referrer-types",children:[(0,i.jsx)("div",{className:"section-title",children:(0,p.__)("Referrers (Page Views)","wp-parsely")}),(0,i.jsx)("div",{className:"multi-percentage-bar",children:Object.entries(t.referrers.types).map((function(e){var t=e[0],r=e[1],s=(0,p.sprintf)(/* translators: 1: Referrer type, 2: Percentage value, %%: Escaped percent sign */ -(0,p.__)("%1$s: %2$s%%","wp-parsely"),n(t),r.viewsPercentage);return(0,i.jsx)("div",{"aria-label":s,className:"bar-fill "+t,style:{width:r.viewsPercentage+"%"}},t)}))}),(0,i.jsxs)("table",{children:[(0,i.jsx)("thead",{children:(0,i.jsx)("tr",{children:Object.keys(t.referrers.types).map((function(e){return(0,i.jsx)("th",{children:n(e)},e)}))})}),(0,i.jsx)("tbody",{children:(0,i.jsx)("tr",{children:Object.entries(t.referrers.types).map((function(e){var t=e[0],n=e[1];return(0,i.jsx)("td",{children:H(n.views)},t)}))})})]})]})}function pe(e){var t=e.data,n=0;return(0,i.jsxs)("div",{className:"section top-referrers",children:[(0,i.jsxs)("table",{children:[(0,i.jsx)("thead",{children:(0,i.jsxs)("tr",{children:[(0,i.jsx)("th",{scope:"col",children:(0,p.__)("Top Referrers","wp-parsely")}),(0,i.jsx)("th",{colSpan:2,scope:"colgroup",children:(0,p.__)("Page Views","wp-parsely")})]})}),(0,i.jsx)("tbody",{children:Object.entries(t.referrers.top).map((function(e){var t=e[0],r=e[1];if("totals"===t)return n=Number(r.viewsPercentage),null;var s=t;"direct"===t&&(s=(0,p.__)("Direct","wp-parsely")) -/* translators: %s: Percentage value, %%: Escaped percent sign */;var a=(0,p.sprintf)((0,p.__)("%s%%","wp-parsely"),r.viewsPercentage);return(0,i.jsxs)("tr",{children:[(0,i.jsx)("th",{scope:"row",children:s}),(0,i.jsx)("td",{children:(0,i.jsx)("div",{"aria-label":a,className:"percentage-bar",style:{"--bar-fill":r.viewsPercentage+"%"}})}),(0,i.jsx)("td",{children:H(r.views)})]},t)}))})]}),(0,i.jsxs)("div",{children:[" ",/* translators: %s: Percentage value, %%: Escaped percent sign */ -(0,p.sprintf)((0,p._n)("%s%% of views came from top referrers.","%s%% of views came from top referrers.",n,"wp-parsely"),n)]})]})}function de(e){var t=e.data,n=(0,i.jsxs)("span",{className:"screen-reader-text",children:[" ",(0,p.__)("(opens in new tab)","wp-parsely")]});return(0,i.jsxs)("div",{className:"section actions",children:[(0,i.jsxs)(s.Button,{href:t.url,rel:"noopener",target:"_blank",variant:"secondary",children:[(0,p.__)("Visit Post","wp-parsely"),n]}),(0,i.jsxs)(s.Button,{href:t.dashUrl,rel:"noopener",target:"_blank",variant:"primary",children:[(0,p.__)("View in Parse.ly","wp-parsely"),n]})]})}var he=function(e){var t=e.filter,n=e.label,s=e.onFilterTypeChange,a=e.onFilterValueChange,o=e.postData,l=function(){return o.tags.length>=1&&o.categories.length>=1||o.tags.length>=1&&o.authors.length>=1||o.categories.length>=1&&o.authors.length>=1},c=function(){return r.Tag===t.type&&o.tags.length>1||r.Section===t.type&&o.categories.length>1||r.Author===t.type&&o.authors.length>1},u=c()&&!l();return(0,i.jsxs)(i.Fragment,{children:[l()&&(0,i.jsx)(fe,{filter:t,label:u?void 0:n,onFilterTypeChange:s,postData:o}),c()&&(0,i.jsx)(ye,{filter:t,label:u?n:void 0,onFilterValueChange:a,postData:o})]})},fe=function(e){var t=e.filter,n=e.label,a=e.onFilterTypeChange,o=e.postData;return(0,i.jsxs)(s.SelectControl,{label:n,onChange:function(e){return a(e)},value:t.type,children:[o.tags.length>=1&&(0,i.jsx)("option",{value:r.Tag,children:(0,p.__)("Tag","wp-parsely")}),o.categories.length>=1&&(0,i.jsx)("option",{value:r.Section,children:(0,p.__)("Section","wp-parsely")}),o.authors.length>=1&&(0,i.jsx)("option",{value:r.Author,children:(0,p.__)("Author","wp-parsely")})]})},ye=function(e){var t=e.filter,n=e.label,a=e.onFilterValueChange,o=e.postData;return(0,i.jsx)(s.ComboboxControl,{label:n,onChange:function(e){return a(e)},options:r.Tag===t.type?o.tags.map((function(e){return{value:e,label:e}})):r.Section===t.type?o.categories.map((function(e){return{value:e,label:e}})):r.Author===t.type?o.authors.map((function(e){return{value:e,label:e}})):[],value:t.value})},ge=function(){return(0,i.jsx)(s.SVG,{"aria-hidden":"true",version:"1.1",viewBox:"0 0 15 15",width:"15",height:"15",xmlns:"http://www.w3.org/2000/svg",children:(0,i.jsx)(s.Path,{d:"M0 14.0025V11.0025L7.5 3.5025L10.5 6.5025L3 14.0025H0ZM12 5.0025L13.56 3.4425C14.15 2.8525 14.15 1.9025 13.56 1.3225L12.68 0.4425C12.09 -0.1475 11.14 -0.1475 10.56 0.4425L9 2.0025L12 5.0025Z"})})},ve=function(){return(0,i.jsxs)(s.SVG,{"aria-hidden":"true",version:"1.1",viewBox:"0 0 16 16",width:"16",height:"16",xmlns:"http://www.w3.org/2000/svg",children:[(0,i.jsx)(s.Path,{d:"M0 3.29592C0 2.73237 0.456853 2.27551 1.02041 2.27551H4.08165C4.50432 2.27551 4.84696 2.61815 4.84696 3.04082C4.84696 3.46349 4.50432 3.80613 4.08165 3.80613H1.53062V11.9694H9.69391V9.6735C9.69391 9.25083 10.0366 8.90819 10.4592 8.90819C10.8819 8.90819 11.2245 9.25083 11.2245 9.6735V12.4796C11.2245 13.0432 10.7677 13.5 10.2041 13.5H1.02041C0.456854 13.5 0 13.0432 0 12.4796V3.29592Z"}),(0,i.jsx)(s.Path,{d:"M12.531 1.22415C12.8299 1.52303 12.8299 2.00759 12.531 2.30646L6.15342 8.68404C5.85455 8.98291 5.36998 8.98291 5.07111 8.68404C4.77224 8.38517 4.77224 7.9006 5.07111 7.60173L11.4487 1.22415C11.7476 0.925282 12.2321 0.925282 12.531 1.22415Z"}),(0,i.jsx)(s.Path,{d:"M6.63268 1.51012C6.63268 1.08745 6.97532 0.744812 7.39799 0.744812H12.2449C12.6676 0.744812 13.0103 1.08745 13.0103 1.51012V6.35708C13.0103 6.77975 12.6676 7.12239 12.2449 7.12239C11.8223 7.12239 11.4796 6.77975 11.4796 6.35708V2.27543H7.39799C6.97532 2.27543 6.63268 1.93279 6.63268 1.51012Z"})]})},we=function(){return(0,i.jsx)(s.SVG,{"aria-hidden":"true",version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 42 42",children:(0,i.jsx)(s.Path,{d:"M15.3,20.1c0,3.1,2.6,5.7,5.7,5.7s5.7-2.6,5.7-5.7s-2.6-5.7-5.7-5.7S15.3,17,15.3,20.1z M23.4,32.4\n\t\t\tC30.1,30.9,40.5,22,40.5,22s-7.7-12-18-13.3c-0.6-0.1-2.6-0.1-3-0.1c-10,1-18,13.7-18,13.7s8.7,8.6,17,9.9\n\t\t\tC19.4,32.6,22.4,32.6,23.4,32.4z M11.1,20.7c0-5.2,4.4-9.4,9.9-9.4s9.9,4.2,9.9,9.4S26.5,30,21,30S11.1,25.8,11.1,20.7z"})})};function me(e){var t=e.metric,n=e.post,r=e.avgEngagedIcon,s=e.viewsIcon;return"views"===t?(0,i.jsxs)("span",{className:"parsely-top-post-metric-data",children:[(0,i.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Number of Views","wp-parsely")}),s,H(n.views.toString())]}):"avg_engaged"===t?(0,i.jsxs)("span",{className:"parsely-top-post-metric-data",children:[(0,i.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Average Time","wp-parsely")}),r,n.avgEngaged]}):(0,i.jsx)("span",{className:"parsely-top-post-metric-data",children:"-"})}function _e(e){var t,n=e.metric,r=e.post;return(0,i.jsxs)("li",{className:"parsely-top-post","data-testid":"parsely-top-post",children:[(0,i.jsxs)("div",{className:"parsely-top-post-title",children:[(0,i.jsxs)("a",{className:"parsely-top-post-stats-link",href:r.dashUrl,target:"_blank",rel:"noreferrer",children:[(0,i.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("View in Parse.ly (opens new tab)","wp-parsely")}),r.title]}),(0,i.jsxs)("a",{className:"parsely-top-post-view-link",href:r.url,target:"_blank",rel:"noreferrer",children:[(0,i.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("View Post (opens new tab)","wp-parsely")}),(0,i.jsx)(ve,{})]}),0!==r.postId&&(0,i.jsxs)("a",{className:"parsely-top-post-edit-link",href:(t=r.postId,"/wp-admin/post.php?post=".concat(t,"&action=edit")),target:"_blank",rel:"noreferrer",children:[(0,i.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Edit Post (opens new tab)","wp-parsely")}),(0,i.jsx)(ge,{})]})]}),(0,i.jsxs)("p",{className:"parsely-top-post-info",children:[(0,i.jsxs)("span",{className:"parsely-top-post-date",children:[(0,i.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Date","wp-parsely")}),J(new Date(r.date))]}),(0,i.jsxs)("span",{className:"parsely-top-post-author",children:[(0,i.jsx)("span",{className:"screen-reader-text",children:(0,p.__)("Author","wp-parsely")}),r.author]}),(0,i.jsx)(me,{metric:n,post:r,viewsIcon:(0,i.jsx)(we,{}),avgEngagedIcon:(0,i.jsx)(s.Dashicon,{icon:"clock"})})]})]})}var be=function(){return be=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]0&&s[s.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!s||l[1]>s[0]&&l[1]=1e4&&(clearInterval(i),n("Telemetry library not loaded"))}),100);else n("Telemetry not enabled")}))},e.prototype.trackEvent=function(t,n){var r;this.isLoaded?(0!==t.indexOf(e.TRACKS_PREFIX)&&(t=e.TRACKS_PREFIX+t),this.isEventNameValid(t)?(n=this.prepareProperties(n),null===(r=this._tkq)||void 0===r||r.push(["recordEvent",t,n])):console.error("Error tracking event: Invalid event name")):console.error("Error tracking event: Telemetry not loaded")},e.prototype.isTelemetryEnabled=function(){return this.isEnabled},e.prototype.isProprietyValid=function(t){return e.PROPERTY_REGEX.test(t)},e.prototype.isEventNameValid=function(t){return e.EVENT_NAME_REGEX.test(t)},e.prototype.prepareProperties=function(e){return(e=this.sanitizeProperties(e)).parsely_version=wpParselyTracksTelemetry.version,wpParselyTracksTelemetry.user&&(e._ut=wpParselyTracksTelemetry.user.type,e._ui=wpParselyTracksTelemetry.user.id),wpParselyTracksTelemetry.vipgo_env&&(e.vipgo_env=wpParselyTracksTelemetry.vipgo_env),this.sanitizeProperties(e)},e.prototype.sanitizeProperties=function(e){var t=this,n={};return Object.keys(e).forEach((function(r){t.isProprietyValid(r)&&(n[r]=e[r])})),n},e.TRACKS_PREFIX="wpparsely_",e.EVENT_NAME_REGEX=/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/,e.PROPERTY_REGEX=/^[a-z_][a-z0-9_]*$/,e}(),h=(g.trackEvent,function(){return(0,s.jsx)(i.SVG,{"aria-hidden":"true",version:"1.1",viewBox:"0 0 15 15",width:"15",height:"15",xmlns:"http://www.w3.org/2000/svg",children:(0,s.jsx)(i.Path,{d:"M0 14.0025V11.0025L7.5 3.5025L10.5 6.5025L3 14.0025H0ZM12 5.0025L13.56 3.4425C14.15 2.8525 14.15 1.9025 13.56 1.3225L12.68 0.4425C12.09 -0.1475 11.14 -0.1475 10.56 0.4425L9 2.0025L12 5.0025Z"})})}),v=function(e){var t=e.size,n=void 0===t?24:t,r=e.className,a=void 0===r?"wp-parsely-icon":r;return(0,s.jsxs)(i.SVG,{className:a,height:n,viewBox:"0 0 60 65",width:n,xmlns:"http://www.w3.org/2000/svg",children:[(0,s.jsx)(i.Path,{fill:"#5ba745",d:"M23.72,51.53c0-.18,0-.34-.06-.52a13.11,13.11,0,0,0-2.1-5.53A14.74,14.74,0,0,0,19.12,43c-.27-.21-.5-.11-.51.22l-.24,3.42c0,.33-.38.35-.49,0l-1.5-4.8a1.4,1.4,0,0,0-.77-.78,23.91,23.91,0,0,0-3.1-.84c-1.38-.24-3.39-.39-3.39-.39-.34,0-.45.21-.25.49l2.06,3.76c.2.27,0,.54-.29.33l-4.51-3.6a3.68,3.68,0,0,0-2.86-.48c-1,.16-2.44.46-2.44.46a.68.68,0,0,0-.39.25.73.73,0,0,0-.14.45S.41,43,.54,44a3.63,3.63,0,0,0,1.25,2.62L6.48,50c.28.2.09.49-.23.37l-4.18-.94c-.32-.12-.5,0-.4.37,0,0,.69,1.89,1.31,3.16a24,24,0,0,0,1.66,2.74,1.34,1.34,0,0,0,1,.52l5,.13c.33,0,.41.38.1.48L7.51,58c-.31.1-.34.35-.07.55a14.29,14.29,0,0,0,3.05,1.66,13.09,13.09,0,0,0,5.9.5,25.13,25.13,0,0,0,4.34-1,9.55,9.55,0,0,1-.08-1.2,9.32,9.32,0,0,1,3.07-6.91"}),(0,s.jsx)(i.Path,{fill:"#5ba745",d:"M59.7,41.53a.73.73,0,0,0-.14-.45.68.68,0,0,0-.39-.25s-1.43-.3-2.44-.46a3.64,3.64,0,0,0-2.86.48l-4.51,3.6c-.26.21-.49-.06-.29-.33l2.06-3.76c.2-.28.09-.49-.25-.49,0,0-2,.15-3.39.39a23.91,23.91,0,0,0-3.1.84,1.4,1.4,0,0,0-.77.78l-1.5,4.8c-.11.32-.48.3-.49,0l-.24-3.42c0-.33-.24-.43-.51-.22a14.74,14.74,0,0,0-2.44,2.47A13.11,13.11,0,0,0,36.34,51c0,.18,0,.34-.06.52a9.26,9.26,0,0,1,3,8.1,24.1,24.1,0,0,0,4.34,1,13.09,13.09,0,0,0,5.9-.5,14.29,14.29,0,0,0,3.05-1.66c.27-.2.24-.45-.07-.55l-3.22-1.17c-.31-.1-.23-.47.1-.48l5-.13a1.38,1.38,0,0,0,1-.52A24.6,24.6,0,0,0,57,52.92c.61-1.27,1.31-3.16,1.31-3.16.1-.33-.08-.49-.4-.37l-4.18.94c-.32.12-.51-.17-.23-.37l4.69-3.34A3.63,3.63,0,0,0,59.46,44c.13-1,.24-2.47.24-2.47"}),(0,s.jsx)(i.Path,{fill:"#5ba745",d:"M46.5,25.61c0-.53-.35-.72-.8-.43l-4.86,2.66c-.45.28-.56-.27-.23-.69l4.66-6.23a2,2,0,0,0,.28-1.68,36.51,36.51,0,0,0-2.19-4.89,34,34,0,0,0-2.81-3.94c-.33-.41-.74-.35-.91.16l-2.28,5.68c-.16.5-.6.48-.59-.05l.28-8.93a2.54,2.54,0,0,0-.66-1.64S35,4.27,33.88,3.27,30.78.69,30.78.69a1.29,1.29,0,0,0-1.54,0s-1.88,1.49-3.12,2.59-2.48,2.35-2.48,2.35A2.5,2.5,0,0,0,23,7.27l.27,8.93c0,.53-.41.55-.58.05l-2.29-5.69c-.17-.5-.57-.56-.91-.14a35.77,35.77,0,0,0-3,4.2,35.55,35.55,0,0,0-2,4.62,2,2,0,0,0,.27,1.67l4.67,6.24c.33.42.23,1-.22.69l-4.87-2.66c-.45-.29-.82-.1-.82.43a18.6,18.6,0,0,0,.83,5.07,20.16,20.16,0,0,0,5.37,7.77c3.19,3,5.93,7.8,7.45,11.08A9.6,9.6,0,0,1,30,49.09a9.31,9.31,0,0,1,2.86.45c1.52-3.28,4.26-8.11,7.44-11.09a20.46,20.46,0,0,0,5.09-7,19,19,0,0,0,1.11-5.82"}),(0,s.jsx)(i.Path,{fill:"#5ba745",d:"M36.12,58.44A6.12,6.12,0,1,1,30,52.32a6.11,6.11,0,0,1,6.12,6.12"})]})},y=function(){return y=Object.assign||function(e){for(var t,n=1,r=arguments.length;nhere.',"wp-parsely"):i.code===Z.ParselyInternalServerError?i.message=(0,c.__)("The Parse.ly API returned an internal server error. Please try again later.","wp-parsely"):i.code===Z.HttpRequestFailed&&i.message.includes("cURL error 28")?i.message=(0,c.__)("The Parse.ly API did not respond in a timely manner. Please try again later.","wp-parsely"):i.code===Z.ParselySchemaValidationFailed?i.message=(0,c.__)("The Parse.ly API returned a validation error. Please try again later.","wp-parsely"):i.code===Z.ParselyUpstreamMalformedResponse&&i.message.includes("Insufficient Storage")?i.message=(0,c.__)("The Parse.ly API couldn't find any relevant data to fulfill the request. Please retry with a different input.","wp-parsely"):i.code===Z.ParselyUpstreamMalformedResponse?i.message=(0,c.__)("The Parse.ly API returned a malformed response. Please try again later.","wp-parsely"):i.code===Z.ParselyUpstreamNotAvailable&&(i.message=(0,c.__)("The Parse.ly API is currently unavailable. Please try again later.","wp-parsely")),i}return K(t,e),t.prototype.Message=function(e){return void 0===e&&(e=null),[Z.PluginCredentialsNotSetMessageDetected,Z.PluginSettingsSiteIdNotSet,Z.PluginSettingsApiSecretNotSet].includes(this.code)?I(e):(this.code===Z.FetchError&&(this.hint=this.Hint((0,c.__)("This error can sometimes be caused by ad-blockers or browser tracking protections. Please add this site to any applicable allow lists and try again.","wp-parsely"))),this.code===Z.ParselyApiForbidden&&(this.hint=this.Hint((0,c.__)("Please ensure that the Site ID and API Secret given in the plugin's settings are correct.","wp-parsely"))),this.code===Z.HttpRequestFailed&&(this.hint=this.Hint((0,c.__)("The Parse.ly API cannot be reached. Please verify that you are online.","wp-parsely"))),(0,s.jsx)(R,{className:null==e?void 0:e.className,testId:"error",children:"

".concat(this.message,"

").concat(this.hint?this.hint:"")}))},t.prototype.Hint=function(e){return'

'.concat((0,c.__)("Hint:","wp-parsely")," ").concat(e,"

")},t}(Error),Q=function(){function e(){}return e.generateSmartLinks=function(e){return t=this,n=arguments,s=function(e,t,n){var r,s,i;return void 0===t&&(t=ce),void 0===n&&(n=oe),function(e,t){var n,r,s,i,a={label:0,sent:function(){if(1&s[0])throw s[1];return s[1]},trys:[],ops:[]};return i={next:l(0),throw:l(1),return:l(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function l(l){return function(o){return function(l){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,l[0]&&(a=0)),a;)try{if(n=1,r&&(s=2&l[0]?r.return:l[0]?r.throw||((s=r.return)&&s.call(r),0):r.next)&&!(s=s.call(r,l[1])).done)return s;switch(r=0,s&&(l=[2&l[0],s.value]),l[0]){case 0:case 1:s=l;break;case 4:return a.label++,{value:l[1],done:!1};case 5:a.label++,r=l[1],l=[0];continue;case 7:l=a.ops.pop(),a.trys.pop();continue;default:if(!((s=(s=a.trys).length>0&&s[s.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!s||l[1]>s[0]&&l[1]0&&s[s.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!s||l[1]>s[0]&&l[1]0)){var e={maxLinksPerPost:p.SmartLinkingMaxLinks,maxLinkWords:p.SmartLinkingMaxLinkWords};R(e)}}),[R,p]);var I=(0,a.useSelect)((function(e){var t=e("core/block-editor"),r=t.getSelectedBlock,s=t.getBlock,i=e("core/editor").getEditedPostContent;return{selectedBlock:n?s(n):r(),postContent:i()}}),[n]),B=I.selectedBlock,M=I.postContent;(0,o.useEffect)((function(){v(null)}),[B]);var F=function(){for(var e=[],t=0;t[type="button"]').forEach((function(e){e.setAttribute("disabled","disabled")}))},z=function(){document.querySelectorAll('.edit-post-header__settings>[type="button"]').forEach((function(e){e.removeAttribute("disabled")})),(0,a.dispatch)("core/editor").unlockPostSaving("wp-parsely-block-overlay")};return(0,s.jsx)("div",{className:"wp-parsely-smart-linking",children:(0,s.jsxs)(i.PanelRow,{className:t,children:[(0,s.jsxs)("div",{className:"smart-linking-text",children:[(0,c.__)("Automatically insert links to your most relevant, top performing content.","wp-parsely"),(0,s.jsxs)(i.Button,{href:"https://docs.parse.ly/plugin-content-helper/#h-smart-linking-beta",target:"_blank",variant:"link",children:[(0,c.__)("Learn more about Parse.ly AI","wp-parsely"),(0,s.jsx)(V,{icon:D,size:18,className:"parsely-external-link-icon"})]})]}),x&&(0,s.jsx)(i.Notice,{status:"info",isDismissible:!1,className:"wp-parsely-content-helper-error",children:x.message}),null!==S&&(0,s.jsx)(i.Notice,{status:"success",isDismissible:!1,className:"wp-parsely-smart-linking-suggested-links",children:/* translators: 1 - number of smart links generated */ +(0,c.sprintf)((0,c.__)("Successfully added %s smart links.","wp-parsely"),S.length)}),(0,s.jsx)($,{disabled:w,selectedBlock:null==B?void 0:B.clientId,onSettingChange:function(e,t){var n,r;d(((n={})[e]=t,n)),R(((r={})[e]=t,r))}}),(0,s.jsxs)("div",{className:"smart-linking-generate",children:[(0,s.jsx)(i.Button,{onClick:function(){return ee(void 0,void 0,void 0,(function(){var e,t,n,r,s;return te(this,(function(i){switch(i.label){case 0:return[4,C(!0)];case 1:return i.sent(),[4,N(null)];case 2:return i.sent(),[4,L(null)];case 3:return i.sent(),g.trackEvent("smart_linking_generate_pressed",{is_full_content:_,selected_block:null!==(s=null==B?void 0:B.name)&&void 0!==s?s:"none",context:l}),[4,F(_?"all":null==B?void 0:B.clientId)];case 4:i.sent(),e=setTimeout((function(){var e;C(!1),g.trackEvent("smart_linking_generate_timeout",{is_full_content:_,selected_block:null!==(e=null==B?void 0:B.name)&&void 0!==e?e:"none",context:l}),G(_?"all":null==B?void 0:B.clientId)}),6e4),i.label=5;case 5:return i.trys.push([5,11,12,15]),t=_||!B,n=[],!(null==B?void 0:B.originalContent)||t?[3,7]:[4,Q.generateSmartLinks(null==B?void 0:B.originalContent,k,P)];case 6:return n=i.sent(),[3,9];case 7:return[4,Q.generateSmartLinks(M,k,P)];case 8:n=i.sent(),i.label=9;case 9:return[4,N(n)];case 10:return i.sent(),function(e){var t;g.trackEvent("smart_linking_applied",{is_full_content:_,selected_block:null!==(t=null==B?void 0:B.name)&&void 0!==t?t:"none",links_count:e.length,context:l});for(var n="",r=n=B&&!_?B.attributes.content:M,s=0,i=e;s(null==d?void 0:d.length)&&o.offset++;var f='').concat(o.text,"");r=X(r,new RegExp("(".concat(o.text,"|]*>").concat(o.text,")")),f,o.offset)}}B&&!_?(0,a.dispatch)("core/block-editor").updateBlockAttributes(B.clientId,{content:r}):(0,a.dispatch)("core/editor").editPost({content:r}),y("success",/* translators: 1 - number of smart links generated */ +(0,c.sprintf)((0,c.__)("%s links applied.","wp-parsely"),e.length),{type:"snackbar",isDismissible:!0})}(n),[3,15];case 11:return r=i.sent(),L(r),y("error",(0,c.__)("There was a problem applying smart links.","wp-parsely"),{type:"snackbar",isDismissible:!0}),[3,15];case 12:return[4,C(!1)];case 13:return i.sent(),[4,G(_?"all":null==B?void 0:B.clientId)];case 14:return i.sent(),clearTimeout(e),[7];case 15:return[2]}}))}))},variant:"primary",isBusy:w,disabled:w,children:w?(0,c.__)("Adding Smart Links…","wp-parsely"):(0,c.__)("Add Smart Links","wp-parsely")}),h&&(0,s.jsxs)(i.Notice,{status:"warning",isDismissible:!0,onRemove:function(){return v(null)},className:"wp-parsely-smart-linking-hint",children:[(0,s.jsx)("strong",{children:(0,c.__)("Hint:","wp-parsely")})," ",h]})]})]})})},se=function(){return se=Object.assign||function(e){for(var t,n=1,r=arguments.length;n=c){var u=t;(s=r/c)%1>1/l&&(u=s>10?1:2),u=parseFloat(s.toFixed(2))===parseFloat(s.toFixed(0))?0:u,i=s.toFixed(u),a=o}l=c})),i+n+a}var me=function(e){var n=e.data,r=e.isLoading,a=(0,o.useState)(t.Views),l=a[0],u=a[1],p=(0,o.useState)(!1),d=p[0],f=p[1];r||delete n.referrers.types.totals;var g=function(e){switch(e){case"social":return(0,c.__)("Social","wp-parsely");case"search":return(0,c.__)("Search","wp-parsely");case"other":return(0,c.__)("Other","wp-parsely");case"internal":return(0,c.__)("Internal","wp-parsely");case"direct":return(0,c.__)("Direct","wp-parsely")}return e},h=(0,c.sprintf)((0,c.__)("By %s","wp-parsely"),C(l)); +/* translators: %s: metric description */return(0,s.jsxs)(ve,{title:(0,c.__)("Categories","wp-parsely"),level:3,subtitle:h,isOpen:d,onClick:function(){return f(!d)},children:[d&&(0,s.jsx)("div",{className:"panel-settings",children:(0,s.jsx)(i.SelectControl,{value:l,prefix:(0,c.__)("By: ","wp-parsely"),onChange:function(e){T(e,t)&&u(e)},children:Object.values(t).map((function(e){return(0,s.jsxs)("option",{value:e,disabled:"avg_engaged"===e,children:[C(e),"avg_engaged"===e&&(0,c.__)(" (coming soon)","wp-parsely")]},e)}))})}),r?(0,s.jsx)("div",{className:"parsely-spinner-wrapper","data-testid":"parsely-spinner-wrapper",children:(0,s.jsx)(i.Spinner,{})}):(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"multi-percentage-bar",children:Object.entries(n.referrers.types).map((function(e){var t=e[0],n=e[1],r=(0,c.sprintf)(/* translators: 1: Referrer type, 2: Percentage value, %%: Escaped percent sign */ +(0,c.__)("%1$s: %2$s%%","wp-parsely"),g(t),n.viewsPercentage);return(0,s.jsx)(i.Tooltip +/* translators: %s: percentage value */,{ +/* translators: %s: percentage value */ +text:"".concat(g(t)," - ").concat((0,c.sprintf)((0,c.__)("%s%%","wp-parsely"),n.viewsPercentage)),delay:150,children:(0,s.jsx)("div",{"aria-label":r,className:"bar-fill "+t,style:{width:n.viewsPercentage+"%"}})},t)}))}),(0,s.jsx)("div",{className:"percentage-bar-labels",children:Object.entries(n.referrers.types).map((function(e){var t=e[0],n=e[1];return(0,s.jsxs)("div",{className:"single-label "+t,children:[(0,s.jsx)("div",{className:"label-color "+t}),(0,s.jsx)("div",{className:"label-text",children:g(t)}),(0,s.jsx)("div",{className:"label-value",children:ye(n.views)})]},t)}))})]})]})},we=(0,u.createElement)(p.SVG,{viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},(0,u.createElement)(p.Path,{d:"M3.99961 13C4.67043 13.3354 4.6703 13.3357 4.67017 13.3359L4.67298 13.3305C4.67621 13.3242 4.68184 13.3135 4.68988 13.2985C4.70595 13.2686 4.7316 13.2218 4.76695 13.1608C4.8377 13.0385 4.94692 12.8592 5.09541 12.6419C5.39312 12.2062 5.84436 11.624 6.45435 11.0431C7.67308 9.88241 9.49719 8.75 11.9996 8.75C14.502 8.75 16.3261 9.88241 17.5449 11.0431C18.1549 11.624 18.6061 12.2062 18.9038 12.6419C19.0523 12.8592 19.1615 13.0385 19.2323 13.1608C19.2676 13.2218 19.2933 13.2686 19.3093 13.2985C19.3174 13.3135 19.323 13.3242 19.3262 13.3305L19.3291 13.3359C19.3289 13.3357 19.3288 13.3354 19.9996 13C20.6704 12.6646 20.6703 12.6643 20.6701 12.664L20.6697 12.6632L20.6688 12.6614L20.6662 12.6563L20.6583 12.6408C20.6517 12.6282 20.6427 12.6108 20.631 12.5892C20.6078 12.5459 20.5744 12.4852 20.5306 12.4096C20.4432 12.2584 20.3141 12.0471 20.1423 11.7956C19.7994 11.2938 19.2819 10.626 18.5794 9.9569C17.1731 8.61759 14.9972 7.25 11.9996 7.25C9.00203 7.25 6.82614 8.61759 5.41987 9.9569C4.71736 10.626 4.19984 11.2938 3.85694 11.7956C3.68511 12.0471 3.55605 12.2584 3.4686 12.4096C3.42484 12.4852 3.39142 12.5459 3.36818 12.5892C3.35656 12.6108 3.34748 12.6282 3.34092 12.6408L3.33297 12.6563L3.33041 12.6614L3.32948 12.6632L3.32911 12.664C3.32894 12.6643 3.32879 12.6646 3.99961 13ZM11.9996 16C13.9326 16 15.4996 14.433 15.4996 12.5C15.4996 10.567 13.9326 9 11.9996 9C10.0666 9 8.49961 10.567 8.49961 12.5C8.49961 14.433 10.0666 16 11.9996 16Z"})),_e=(0,u.createElement)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,u.createElement)(p.Path,{d:"M15.5 9.5a1 1 0 100-2 1 1 0 000 2zm0 1.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zm-2.25 6v-2a2.75 2.75 0 00-2.75-2.75h-4A2.75 2.75 0 003.75 15v2h1.5v-2c0-.69.56-1.25 1.25-1.25h4c.69 0 1.25.56 1.25 1.25v2h1.5zm7-2v2h-1.5v-2c0-.69-.56-1.25-1.25-1.25H15v-1.5h2.5A2.75 2.75 0 0120.25 15zM9.5 8.5a1 1 0 11-2 0 1 1 0 012 0zm1.5 0a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z",fillRule:"evenodd"})),be=(0,u.createElement)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,u.createElement)(p.Path,{d:"M12 4V2.2L9 4.8l3 2.5V5.5c3.6 0 6.5 2.9 6.5 6.5 0 2.9-1.9 5.3-4.5 6.2v.2l-.1-.2c-.4.1-.7.2-1.1.2l.2 1.5c.3 0 .6-.1 1-.2 3.5-.9 6-4 6-7.7 0-4.4-3.6-8-8-8zm-7.9 7l1.5.2c.1-1.2.5-2.3 1.2-3.2l-1.1-.9C4.8 8.2 4.3 9.6 4.1 11zm1.5 1.8l-1.5.2c.1.7.3 1.4.5 2 .3.7.6 1.3 1 1.8l1.2-.8c-.3-.5-.6-1-.8-1.5s-.4-1.1-.4-1.7zm1.5 5.5c1.1.9 2.4 1.4 3.8 1.6l.2-1.5c-1.1-.1-2.2-.5-3.1-1.2l-.9 1.1z"})),xe=(0,u.createElement)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,u.createElement)(p.Path,{d:"M11 13h2v-2h-2v2zm-6 0h2v-2H5v2zm12-2v2h2v-2h-2z"})),Se=function(){return Se=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&s[s.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!s||l[1]>s[0]&&l[1]1?[2,Promise.reject(new Y((0,c.sprintf)(/* translators: URL of the published post */ +(0,c.__)("Multiple results were returned for the post %s by the Parse.ly API.","wp-parsely"),t),Z.ParselyApiReturnedTooManyResults))]:[2,n.data[0]]}}))}))},e.prototype.fetchReferrerDataFromWpEndpoint=function(e,t,n){return Re(this,void 0,void 0,(function(){var r,s;return Ie(this,(function(i){switch(i.label){case 0:return i.trys.push([0,2,,3]),[4,x()({path:(0,q.addQueryArgs)("/wp-parsely/v1/referrers/post/detail",Ae(Ae({},Oe(e)),{itm_source:this.itmSource,total_views:n,url:t}))})];case 1:return r=i.sent(),[3,3];case 2:return s=i.sent(),[2,Promise.reject(new Y(s.message,s.code))];case 3:return(null==r?void 0:r.error)?[2,Promise.reject(new Y(r.error.message,Z.ParselyApiResponseContainsError))]:[2,r.data]}}))}))},e}(),Me=function(){return Me=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&s[s.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!s||l[1]>s[0]&&l[1]0&&e.retryFetch?[4,new Promise((function(e){return setTimeout(e,500)}))]:[3,3];case 1:return n.sent(),[4,t(r-1)];case 2:return n.sent(),[3,4];case 3:d(e),l(!1),n.label=4;case 4:return[2]}}))}))})),[2]}))}))};return l(!0),t(1),function(){d(void 0)}}),[n]),(0,s.jsxs)("div",{className:"wp-parsely-performance-panel",children:[(0,s.jsx)(ve,{title:(0,c.__)("Performance Stats","wp-parsely"),icon:he,dropdownChildren:function(e){var t=e.onClose;return(0,s.jsx)(ze,{onClose:t})},children:(0,s.jsx)("div",{className:"panel-settings",children:(0,s.jsx)(i.SelectControl,{size:"__unstable-large",value:m.PerformanceStatsSettings.Period,prefix:(0,s.jsx)(i.__experimentalInputControlPrefixWrapper,{children:(0,c.__)("Period: ","wp-parsely")}),onChange:function(t){T(t,e)&&(w({PerformanceStatsSettings:Me(Me({},m.PerformanceStatsSettings),{Period:t})}),g.trackEvent("editor_sidebar_performance_period_changed",{period:t}))},children:Object.values(e).map((function(e){return(0,s.jsx)("option",{value:e,children:E(e)},e)}))})})}),p?p.Message():(0,s.jsxs)(s.Fragment,{children:[He(m,"overview")&&(0,s.jsx)(Le,{data:h,isLoading:a}),He(m,"categories")&&(0,s.jsx)(me,{data:h,isLoading:a}),He(m,"referrers")&&(0,s.jsx)(Ne,{data:h,isLoading:a})]}),window.wpParselyPostUrl&&(0,s.jsx)(i.Button,{className:"wp-parsely-view-post",variant:"primary",onClick:function(){g.trackEvent("editor_sidebar_view_post_pressed")},href:window.wpParselyPostUrl,rel:"noopener",target:"_blank",children:(0,c.__)("View this in Parse.ly","wp-parsely")})]})},We=function(e){var t=e.period;return(0,s.jsx)(i.Panel,{children:(0,s.jsx)(M,{children:(0,s.jsx)(Ue,{period:t})})})},Ze=window.wp.coreData,$e=window.wp.editor,qe=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var s=0;for(r=Object.getOwnPropertySymbols(e);s=1&&(0,s.jsx)(i.__experimentalToggleGroupControlOption,{value:r.Tag,label:(0,c.__)("Tag","wp-parsely")}),a.categories.length>=1&&(0,s.jsx)(i.__experimentalToggleGroupControlOption,{value:r.Section,label:(0,c.__)("Section","wp-parsely")}),(0,s.jsx)(i.__experimentalToggleGroupControlOption,{value:r.Author,label:(0,c.__)("Author","wp-parsely")})]})})},Ye=function(e){var t=e.filter,n=e.postData,a=qe(e,["filter","postData"]);return(0,s.jsx)("div",{className:"related-posts-filter-values",children:(0,s.jsx)(i.ComboboxControl,{allowReset:!0,onChange:function(e){return a.onFilterValueChange(e)},options:r.Tag===t.type?n.tags.map((function(e){return{value:e,label:e}})):r.Section===t.type?n.categories.map((function(e){return{value:e,label:e}})):r.Author===t.type?n.authors.map((function(e){return{value:e,label:e}})):[],value:t.value})})},Qe=function(e){var t=e.filter,n=e.postData,i=e.label,a=qe(e,["filter","postData","label"]);return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(Ke,{filter:t,label:i,onFilterTypeChange:a.onFilterTypeChange,postData:n}),(r.Tag===t.type&&n.tags.length>1||r.Section===t.type&&n.categories.length>1||r.Author===t.type&&n.authors.length>1)&&(0,s.jsx)(Ye,{filter:t,onFilterValueChange:a.onFilterValueChange,postData:n})]})},Xe=(0,u.createElement)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,u.createElement)(p.Path,{d:"M10 17.389H8.444A5.194 5.194 0 1 1 8.444 7H10v1.5H8.444a3.694 3.694 0 0 0 0 7.389H10v1.5ZM14 7h1.556a5.194 5.194 0 0 1 0 10.39H14v-1.5h1.556a3.694 3.694 0 0 0 0-7.39H14V7Zm-4.5 6h5v-1.5h-5V13Z"})),Je=(0,u.createElement)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,u.createElement)(p.Path,{fillRule:"evenodd",clipRule:"evenodd",d:"M5.625 5.5h9.75c.069 0 .125.056.125.125v9.75a.125.125 0 0 1-.125.125h-9.75a.125.125 0 0 1-.125-.125v-9.75c0-.069.056-.125.125-.125ZM4 5.625C4 4.728 4.728 4 5.625 4h9.75C16.273 4 17 4.728 17 5.625v9.75c0 .898-.727 1.625-1.625 1.625h-9.75A1.625 1.625 0 0 1 4 15.375v-9.75Zm14.5 11.656v-9H20v9C20 18.8 18.77 20 17.251 20H6.25v-1.5h11.001c.69 0 1.249-.528 1.249-1.219Z"}));function et(e){var t=e.metric,n=e.post,r=e.avgEngagedIcon,i=e.viewsIcon;return"views"===t?(0,s.jsxs)("span",{className:"parsely-post-metric-data",children:[(0,s.jsx)("span",{className:"screen-reader-text",children:(0,c.__)("Number of Views","wp-parsely")}),i,ye(n.views.toString())]}):"avg_engaged"===t?(0,s.jsxs)("span",{className:"parsely-post-metric-data",children:[(0,s.jsx)("span",{className:"screen-reader-text",children:(0,c.__)("Average Time","wp-parsely")}),r,n.avgEngaged]}):(0,s.jsx)("span",{className:"parsely-post-metric-data",children:"-"})}var tt,nt=function(){return(0,s.jsx)(i.SVG,{xmlns:"http://www.w3.org/2000/svg",width:"1",height:"40",viewBox:"0 0 1 40",fill:"none",children:(0,s.jsx)(i.Rect,{width:"1",height:"40",fill:"#cccccc"})})},rt=function(e){var t=e.metric,n=e.post,r=e.postContent,l=(0,a.useDispatch)("core/notices").createNotice,o=!1;return r&&(o=r.includes(n.rawUrl)),(0,s.jsxs)("div",{className:"related-post-single","data-testid":"related-post-single",children:[(0,s.jsx)("div",{className:"related-post-title",children:(0,s.jsxs)("a",{href:n.url,target:"_blank",rel:"noreferrer",children:[(0,s.jsx)("span",{className:"screen-reader-text",children:(0,c.__)("View on website (opens new tab)","wp-parsely")}),n.title]})}),(0,s.jsx)("div",{className:"related-post-actions",children:(0,s.jsxs)("div",{className:"related-post-info",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"related-post-metric",children:(0,s.jsx)(et,{metric:t,post:n,viewsIcon:(0,s.jsx)(V,{icon:we}),avgEngagedIcon:(0,s.jsx)(i.Dashicon,{icon:"clock",size:24})})}),o&&(0,s.jsx)("div",{className:"related-post-linked",children:(0,s.jsx)(i.Tooltip,{text:(0,c.__)("This post is linked in the content","wp-parsely"),children:(0,s.jsx)(V,{icon:Xe,size:24})})})]}),(0,s.jsx)(nt,{}),(0,s.jsxs)("div",{children:[(0,s.jsx)(i.Button,{icon:Je,iconSize:24,onClick:function(){navigator.clipboard.writeText(n.rawUrl).then((function(){l("success",(0,c.__)("URL copied to clipboard","wp-parsely"),{type:"snackbar",isDismissible:!0})}))},label:(0,c.__)("Copy URL to clipboard","wp-parsely")}),(0,s.jsx)(i.Button,{icon:(0,s.jsx)(v,{}),iconSize:18,href:n.dashUrl,target:"_blank",label:(0,c.__)("View in Parse.ly","wp-parsely")})]})]})})]})},st=function(){return st=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&s[s.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!s||l[1]>s[0]&&l[1]0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]0&&n.retryFetch?[4,new Promise((function(e){return setTimeout(e,500)}))]:[3,3];case 1:return t.sent(),[4,i(e-1)];case 2:return t.sent(),[3,4];case 3:y(!1),w(n),t.label=4;case 4:return[2]}}))}))})),[2]}))}))},s=r.Tag===S.type,o=r.Unavailable===S.type,u=0===c.tags.length,p=s&&!c.tags.includes(S.value);return y(!0),o||s&&u?E((e="",n=r.Unavailable,c.tags.length>=1?(n=r.Tag,e=c.tags[0]):c.categories.length>=1?(n=r.Section,e=c.categories[0]):(n=r.Author,e=c.authors[0]),{type:n,value:e})):p?E({type:r.Tag,value:c.tags[0]}):i(1),function(){y(!1),P([]),b(""),w(void 0)}}),[l,a,S,c]);var C=(0,i.jsx)("div",{className:"parsely-spinner-wrapper","data-testid":"parsely-spinner-wrapper",children:(0,i.jsx)(s.Spinner,{})}),N=(0,i.jsx)(he,{filter:S,label:(0,p.__)("Filter by","wp-parsely"),onFilterTypeChange:function(e){if(F(e,r)){var t="",n=e;r.Tag===n&&(t=c.tags[0]),r.Section===n&&(t=c.categories[0]),r.Author===n&&(t=c.authors[0]),""!==t&&(o(n,t),E({type:n,value:t}),h.trackEvent("related_top_posts_filter_type_changed",{filter_type:n}))}},onFilterValueChange:function(e){"string"==typeof e&&(o(S.type,e),E(Te(Te({},S),{value:e})))},postData:c});return v?(0,i.jsxs)(i.Fragment,{children:[N,v.Message({className:"parsely-top-posts-descr"})]}):(0,i.jsxs)(i.Fragment,{children:[N,f?C:(0,i.jsxs)("div",{className:"parsely-top-posts-wrapper",children:[(0,i.jsx)("p",{className:"parsely-top-posts-descr","data-testid":"parsely-top-posts-descr",children:_}),(0,i.jsx)("ol",{className:"parsely-top-posts",children:j.map((function(e){return(0,i.jsx)(_e,{metric:a,post:e},e.id)}))})]})]})}var Ne,ke=(0,g.createElement)(v.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,g.createElement)(v.Path,{d:"m19 7.5h-7.628c-.3089-.87389-1.1423-1.5-2.122-1.5-.97966 0-1.81309.62611-2.12197 1.5h-2.12803v1.5h2.12803c.30888.87389 1.14231 1.5 2.12197 1.5.9797 0 1.8131-.62611 2.122-1.5h7.628z"}),(0,g.createElement)(v.Path,{d:"m19 15h-2.128c-.3089-.8739-1.1423-1.5-2.122-1.5s-1.8131.6261-2.122 1.5h-7.628v1.5h7.628c.3089.8739 1.1423 1.5 2.122 1.5s1.8131-.6261 2.122-1.5h2.128z"})),Oe=function(e){var t=e.isLoading,n=e.isOpen,r=e.onPersonaChange,a=e.onSettingChange,o=e.onToneChange,l=e.persona,c=e.tone,d=(0,u.useState)(n),f=d[0],y=d[1];return(0,i.jsxs)("div",{className:"parsely-write-titles-settings",children:[(0,i.jsxs)("div",{className:"parsely-write-titles-settings-header",children:[(0,i.jsx)(I,{size:20}),(0,i.jsx)(s.BaseControl,{id:"parsely-write-titles-settings",className:"parsely-write-titles-settings-header-label",label:(0,p.__)("Parse.ly AI Settings","wp-parsely"),children:(0,i.jsx)(s.Button,{label:(0,p.__)("Change Tone & Persona","wp-parsely"),icon:ke,onClick:function(){a("TitleSuggestionsSettingsOpen",!f),y(!f),h.trackEvent("title_suggestions_ai_settings_toggled",{is_active:!f})},isPressed:f,size:"small"})})]}),f&&(0,i.jsxs)("div",{className:"parsely-write-titles-settings-body",children:[(0,i.jsx)(A,{tone:c,label:N(c),onChange:function(e){o(e)},onDropdownChange:function(e){h.trackEvent("title_suggestions_ai_tone_changed",{tone:e})},disabled:t,allowCustom:!0}),(0,i.jsx)(T,{persona:l,label:x(l),onChange:function(e){r(e)},onDropdownChange:function(e){h.trackEvent("title_suggestions_ai_persona_changed",{persona:e})},disabled:t,allowCustom:!0})]})]})},Ae=(0,g.createElement)(v.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,g.createElement)(v.Path,{d:"M16.7 7.1l-6.3 8.5-3.3-2.5-.9 1.2 4.5 3.4L17.9 8z"})),Re=(0,g.createElement)(v.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,g.createElement)(v.Path,{d:"m21.5 9.1-6.6-6.6-4.2 5.6c-1.2-.1-2.4.1-3.6.7-.1 0-.1.1-.2.1-.5.3-.9.6-1.2.9l3.7 3.7-5.7 5.7v1.1h1.1l5.7-5.7 3.7 3.7c.4-.4.7-.8.9-1.2.1-.1.1-.2.2-.3.6-1.1.8-2.4.6-3.6l5.6-4.1zm-7.3 3.5.1.9c.1.9 0 1.8-.4 2.6l-6-6c.8-.4 1.7-.5 2.6-.4l.9.1L15 4.9 19.1 9l-4.9 3.6z"})),Le=(0,g.createElement)(v.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,g.createElement)(v.Path,{d:"M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z"})),Ie=(0,g.createElement)(v.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,g.createElement)(v.Path,{d:"M18.3 11.7c-.6-.6-1.4-.9-2.3-.9H6.7l2.9-3.3-1.1-1-4.5 5L8.5 16l1-1-2.7-2.7H16c.5 0 .9.2 1.3.5 1 1 1 3.4 1 4.5v.3h1.5v-.2c0-1.5 0-4.3-1.5-5.7z"})),Fe=function(){return Fe=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]0?"secondary":"primary",isBusy:b,onClick:function(){return $e(void 0,void 0,void 0,(function(){return We(this,(function(e){switch(e.label){case 0:return!1!==b?[3,2]:(h.trackEvent("title_suggestions_generate_pressed",{request_more:j.length>0,total_titles:j.length,total_pinned:j.filter((function(e){return e.isPinned})).length,tone:y,persona:w}),[4,(t=Ne.PostTitle,n=A,r=y,i=w,$e(void 0,void 0,void 0,(function(){var e,s,a;return We(this,(function(o){switch(o.label){case 0:return[4,C(!0)];case 1:o.sent(),e=new Ue,o.label=2;case 2:return o.trys.push([2,5,,6]),[4,e.generateTitles(n,3,r,i)];case 3:return s=o.sent(),[4,E(t,s)];case 4:return o.sent(),[3,6];case 5:return a=o.sent(),d(a),[3,6];case 6:return[4,C(!1)];case 7:return o.sent(),[2]}}))})))]);case 1:e.sent(),e.label=2;case 2:return[2]}var t,n,r,i}))}))},children:[b&&(0,p.__)("Generating Titles…","wp-parsely"),!b&&j.length>0&&(0,p.__)("Generate More","wp-parsely"),!b&&0===j.length&&(0,p.__)("Generate Titles","wp-parsely")]})}),F=(0,i.jsxs)("div",{className:"parsely-write-titles-title-suggestions-container",children:[void 0!==T&&(0,i.jsx)(He,{title:T,type:Ne.PostTitle,isOriginal:!0}),j.map((function(e){return(0,i.jsx)(He,{title:e,type:Ne.PostTitle},e.id)}))]}),D=(0,i.jsxs)("div",{className:"parsely-write-titles-accepted-title-container",children:[(0,i.jsx)("div",{className:"parsely-write-titles-text",children:(0,p.__)("Replace the current post title with the following?","wp-parsely")}),(0,i.jsx)("div",{className:"parsely-write-titles-accepted-title",children:null==P?void 0:P.title}),(0,i.jsxs)("div",{className:"parsely-write-titles-accepted-title-actions",children:[(0,i.jsx)(s.Button,{variant:"secondary",onClick:function(){var e;k(Ne.PostTitle,void 0),h.trackEvent("title_suggestions_cancel_pressed",{original_title:R,canceled_title:null!==(e=null==P?void 0:P.title)&&void 0!==e?e:""})},children:(0,p.__)("Cancel","wp-parsely")}),(0,i.jsx)(s.Button,{variant:"primary",onClick:function(){return $e(void 0,void 0,void 0,(function(){return We(this,(function(e){switch(e.label){case 0:return[4,O(Ne.PostTitle,R)];case 1:return e.sent(),(0,o.dispatch)("core/editor").editPost({title:null==P?void 0:P.title}),P?[4,(0,o.dispatch)(ze).pinTitle(Ne.PostTitle,P)]:[3,3];case 2:e.sent(),h.trackEvent("title_suggestions_accept_pressed",{old_title:R,new_title:P.title}),e.label=3;case 3:return[4,k(Ne.PostTitle,void 0)];case 4:return e.sent(),[2]}}))}))},children:(0,p.__)("Replace","wp-parsely")})]})]});return c?c.Message():(0,i.jsx)(s.PanelRow,{children:(0,i.jsxs)("div",{className:"parsely-write-titles-wrapper",children:[0===j.length&&void 0===P&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("div",{className:"parsely-write-titles-text",children:(0,p.__)("Use Parse.ly AI to generate a title for your post.","wp-parsely")}),L,I]}),00&&s[s.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!s||l[1]>s[0]&&l[1]0&&t.retryFetch?[4,new Promise((function(e){return setTimeout(e,500)}))]:[3,3];case 1:return r.sent(),[4,n(e-1)];case 2:return r.sent(),[3,4];case 3:S(!1),L(t),r.label=4;case 4:return[2]}}))}))})),[2]}))}))},s=r.Tag===D.type,i=r.Section===D.type,a=r.Unavailable===D.type,l=0===h.tags.length,o=0===h.categories.length,c=s&&!h.tags.includes(D.value),u=i&&!h.categories.includes(D.value);return S(!0),a||s&&l||i&&o?Object.values(h).every((function(e){return 0===e.length}))||F((e="",t=r.Unavailable,h.tags.length>=1?(t=r.Tag,e=h.tags[0]):h.categories.length>=1?(t=r.Section,e=h.categories[0]):(t=r.Author,e=h.authors[0]),{type:t,value:e})):c?F({type:r.Tag,value:h.tags[0]}):u?F({type:r.Section,value:h.categories[0]}):n(1),function(){S(!1),M([]),R(""),L(void 0)}}),[p,d,D,h]),(0,s.jsxs)("div",{className:"wp-parsely-related-posts",children:[(0,s.jsx)("div",{className:"related-posts-description",children:(0,c.__)("Find top-performing related posts based on a key metric.","wp-parsely")}),(0,s.jsxs)("div",{className:"related-posts-body",children:[(0,s.jsxs)("div",{className:"related-posts-settings",children:[(0,s.jsx)(i.SelectControl,{size:"__unstable-large",onChange:function(e){var n;T(n=e,t)&&(u({RelatedPostsMetric:n}),g.trackEvent("related_posts_metric_changed",{metric:n}))},prefix:(0,s.jsx)(i.__experimentalInputControlPrefixWrapper,{children:(0,c.__)("Metric: ","wp-parsely")}),value:d,children:Object.values(t).map((function(e){return(0,s.jsx)("option",{value:e,children:C(e)},e)}))}),(0,s.jsx)(i.SelectControl,{size:"__unstable-large",value:p,prefix:(0,s.jsxs)(i.__experimentalInputControlPrefixWrapper,{children:[(0,c.__)("Period: ","wp-parsely")," "]}),onChange:function(t){return function(t){T(t,e)&&(u({RelatedPostsPeriod:t}),g.trackEvent("related_posts_period_changed",{period:t}))}(t)},children:Object.values(e).map((function(e){return(0,s.jsx)("option",{value:e,children:E(e)},e)}))})]}),(0,s.jsx)("div",{className:"related-posts-filter-settings",children:(0,s.jsx)(Qe,{label:(0,c.__)("Filter by","wp-parsely"),filter:D,onFilterTypeChange:function(e){if(T(e,r)){var t="",n=e;r.Tag===n&&(t=h.tags[0]),r.Section===n&&(t=h.categories[0]),r.Author===n&&(t=h.authors[0]),""!==t&&(W(n,t),F({type:n,value:t}),g.trackEvent("related_posts_filter_type_changed",{filter_type:n}))}},onFilterValueChange:function(e){"string"==typeof e&&(W(D.type,e),F(ot(ot({},D),{value:e})))},postData:h})}),(0,s.jsxs)("div",{className:"related-posts-wrapper",children:[(0,s.jsx)("div",{children:(0,s.jsx)("p",{className:"related-posts-descr","data-testid":"parsely-related-posts-descr",children:r.Tag===D.type?(0,c.sprintf)(/* translators: 1: tag name, 2: period */ +(0,c.__)("Top related posts with the “%1$s” tag in the %2$s.","wp-parsely"),D.value,E(p,!0)):r.Section===D.type?(0,c.sprintf)(/* translators: 1: section name, 2: period */ +(0,c.__)("Top related posts in the “%1$s” section in the %2$s.","wp-parsely"),D.value,E(p,!0)):r.Author===D.type?(0,c.sprintf)(/* translators: 1: author name, 2: period */ +(0,c.__)("Top related posts by %1$s in the %2$s.","wp-parsely"),D.value,E(p,!0)):null!=A?A:""})}),k&&k.Message(),x&&(0,s.jsx)("div",{className:"related-posts-loading-message","data-testid":"parsely-related-posts-loading-message",children:(0,c.__)("Loading…","wp-parsely")}),!x&&!k&&0===B.length&&(0,s.jsx)("div",{className:"related-posts-empty",children:(0,c.__)("No related posts found.","wp-parsely")}),!x&&B.length>0&&(0,s.jsx)("div",{className:"related-posts-list",children:B.map((function(e){return(0,s.jsx)(rt,{metric:d,post:e,postContent:H},e.id)}))})]})]})]})},dt=(0,u.createElement)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,u.createElement)(p.Path,{d:"m19 7-3-3-8.5 8.5-1 4 4-1L19 7Zm-7 11.5H5V20h7v-1.5Z"})),ft=function(){return(0,s.jsx)(i.SVG,{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 18 18",fill:"none",children:(0,s.jsx)(i.Path,{fillRule:"evenodd",clipRule:"evenodd",d:"M13.5034 7.91642L9 12.0104L4.49662 7.91642L5.25337 7.08398L8.99999 10.49L12.7466 7.08398L13.5034 7.91642Z",fill:"#1E1E1E"})})},gt={journalist:{label:(0,c.__)("Journalist","wp-parsely")},editorialWriter:{label:(0,c.__)("Editorial Writer","wp-parsely")},investigativeReporter:{label:(0,c.__)("Investigative Reporter","wp-parsely")},techAnalyst:{label:(0,c.__)("Tech Analyst","wp-parsely")},businessAnalyst:{label:(0,c.__)("Business Analyst","wp-parsely")},culturalCommentator:{label:(0,c.__)("Cultural Commentator","wp-parsely")},scienceCorrespondent:{label:(0,c.__)("Science Correspondent","wp-parsely")},politicalAnalyst:{label:(0,c.__)("Political Analyst","wp-parsely")},healthWellnessAdvocate:{label:(0,c.__)("Health and Wellness Advocate","wp-parsely")},environmentalJournalist:{label:(0,c.__)("Environmental Journalist","wp-parsely")},custom:{label:(0,c.__)("Custom Persona","wp-parsely"),icon:dt}},ht=Object.keys(gt),vt=function(e){return"custom"===e||""===e?gt.custom.label:yt(e)?e:gt[e].label},yt=function(e){return!ht.includes(e)||"custom"===e},mt=function(e){var t=e.value,n=e.onChange,r=(0,o.useState)(""),a=r[0],l=r[1],u=(0,N.useDebounce)(n,500);return(0,s.jsx)("div",{className:"parsely-persona-selector-custom",children:(0,s.jsx)(i.TextControl,{value:a||t,placeholder:(0,c.__)("Enter a custom persona…","wp-parsely"),onChange:function(e){if(""===e)return n(""),void l("");e.length>32&&(e=e.slice(0,32)),u(e),l(e)}})})},wt=function(e){var t=e.persona,n=e.value,r=void 0===n?(0,c.__)("Select a persona…","wp-parsely"):n,a=e.label,l=void 0===a?(0,c.__)("Persona","wp-parsely"):a,o=e.onChange,u=e.onDropdownChange,p=e.disabled,d=void 0!==p&&p,f=e.allowCustom,g=void 0!==f&&f;return(0,s.jsxs)(i.Disabled,{isDisabled:d,children:[l&&(0,s.jsx)("div",{className:"wp-parsely-dropdown-label",children:l}),(0,s.jsx)(i.DropdownMenu,{label:(0,c.__)("Persona","wp-parsely"),className:"parsely-persona-selector-dropdown"+(d?" is-disabled":""),popoverProps:{className:"wp-parsely-popover"},toggleProps:{children:(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("div",{className:"parsely-persona-selector-label",children:yt(t)?gt.custom.label:r}),(0,s.jsx)(ft,{})]})},children:function(e){var n=e.onClose;return(0,s.jsx)(i.MenuGroup,{label:(0,c.__)("Persona","wp-parsely"),children:(0,s.jsx)(s.Fragment,{children:ht.map((function(e){if(!g&&"custom"===e)return null;var r=gt[e],a=e===t||yt(t)&&"custom"===e;return(0,s.jsxs)(i.MenuItem,{isSelected:a,className:a?"is-selected":"",role:"menuitemradio",onClick:function(){null==u||u(e),o(e),n(),"custom"===e&&setTimeout((function(){var e=document.querySelector(".parsely-persona-selector-custom input");e&&e.focus()}),0)},children:[r.icon&&(0,s.jsx)(V,{icon:r.icon}),r.label]},e)}))})})}}),g&&yt(t)&&(0,s.jsx)(mt,{onChange:function(e){o(""!==e?e:"custom")},value:"custom"===t?"":t})]})},_t={neutral:{label:(0,c.__)("Neutral","wp-parsely")},formal:{label:(0,c.__)("Formal","wp-parsely")},humorous:{label:(0,c.__)("Humorous","wp-parsely")},confident:{label:(0,c.__)("Confident","wp-parsely")},provocative:{label:(0,c.__)("Provocative","wp-parsely")},serious:{label:(0,c.__)("Serious","wp-parsely")},inspirational:{label:(0,c.__)("Inspirational","wp-parsely")},skeptical:{label:(0,c.__)("Skeptical","wp-parsely")},conversational:{label:(0,c.__)("Conversational","wp-parsely")},analytical:{label:(0,c.__)("Analytical","wp-parsely")},custom:{label:(0,c.__)("Custom Tone","wp-parsely"),icon:dt}},bt=Object.keys(_t),xt=function(e){return"custom"===e||""===e?_t.custom.label:St(e)?e:_t[e].label},St=function(e){return!bt.includes(e)||"custom"===e},Pt=function(e){var t=e.value,n=e.onChange,r=(0,o.useState)(""),a=r[0],l=r[1],u=(0,N.useDebounce)(n,500);return(0,s.jsx)("div",{className:"parsely-tone-selector-custom",children:(0,s.jsx)(i.TextControl,{value:a||t,placeholder:(0,c.__)("Enter a custom tone","wp-parsely"),onChange:function(e){if(""===e)return n(""),void l("");e.length>32&&(e=e.slice(0,32)),u(e),l(e)}})})},jt=function(e){var t=e.tone,n=e.value,r=void 0===n?(0,c.__)("Select a tone","wp-parsely"):n,a=e.label,l=void 0===a?(0,c.__)("Tone","wp-parsely"):a,o=e.onChange,u=e.onDropdownChange,p=e.disabled,d=void 0!==p&&p,f=e.allowCustom,g=void 0!==f&&f;return(0,s.jsxs)(i.Disabled,{isDisabled:d,children:[(0,s.jsx)("div",{className:"wp-parsely-dropdown-label",children:l}),(0,s.jsx)(i.DropdownMenu,{label:(0,c.__)("Tone","wp-parsely"),className:"parsely-tone-selector-dropdown"+(d?" is-disabled":""),popoverProps:{className:"wp-parsely-popover"},toggleProps:{children:(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("div",{className:"parsely-tone-selector-label",children:St(t)?_t.custom.label:r}),(0,s.jsx)(ft,{})]})},children:function(e){var n=e.onClose;return(0,s.jsx)(i.MenuGroup,{label:(0,c.__)("Select a tone","wp-parsely"),children:(0,s.jsx)(s.Fragment,{children:bt.map((function(e){if(!g&&"custom"===e)return null;var r=_t[e],a=e===t||St(t)&&"custom"===e;return(0,s.jsxs)(i.MenuItem,{isSelected:a,className:a?"is-selected":"",role:"menuitemradio",onClick:function(){null==u||u(e),o(e),n(),"custom"===e&&setTimeout((function(){var e=document.querySelector(".parsely-tone-selector-custom input");e&&e.focus()}),0)},children:[r.icon&&(0,s.jsx)(V,{icon:r.icon}),r.label]},e)}))})})}}),g&&St(t)&&(0,s.jsx)(Pt,{onChange:function(e){o(""!==e?e:"custom")},value:"custom"===t?"":t})]})},kt=(0,u.createElement)(p.SVG,{width:"24",height:"24",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},(0,u.createElement)(p.Path,{d:"M10.97 10.159a3.382 3.382 0 0 0-2.857.955l1.724 1.723-2.836 2.913L7 17h1.25l2.913-2.837 1.723 1.723a3.38 3.38 0 0 0 .606-.825c.33-.63.446-1.343.35-2.032L17 10.695 13.305 7l-2.334 3.159Z"})),Tt=(0,u.createElement)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,u.createElement)(p.Path,{d:"M18.3 11.7c-.6-.6-1.4-.9-2.3-.9H6.7l2.9-3.3-1.1-1-4.5 5L8.5 16l1-1-2.7-2.7H16c.5 0 .9.2 1.3.5 1 1 1 3.4 1 4.5v.3h1.5v-.2c0-1.5 0-4.3-1.5-5.7z"})),Et=(0,u.createElement)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,u.createElement)(p.Path,{fillRule:"evenodd",clipRule:"evenodd",d:"M12 5.5A2.25 2.25 0 0 0 9.878 7h4.244A2.251 2.251 0 0 0 12 5.5ZM12 4a3.751 3.751 0 0 0-3.675 3H5v1.5h1.27l.818 8.997a2.75 2.75 0 0 0 2.739 2.501h4.347a2.75 2.75 0 0 0 2.738-2.5L17.73 8.5H19V7h-3.325A3.751 3.751 0 0 0 12 4Zm4.224 4.5H7.776l.806 8.861a1.25 1.25 0 0 0 1.245 1.137h4.347a1.25 1.25 0 0 0 1.245-1.137l.805-8.861Z"})),Ct=(0,u.createElement)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,u.createElement)(p.Path,{d:"m21.5 9.1-6.6-6.6-4.2 5.6c-1.2-.1-2.4.1-3.6.7-.1 0-.1.1-.2.1-.5.3-.9.6-1.2.9l3.7 3.7-5.7 5.7v1.1h1.1l5.7-5.7 3.7 3.7c.4-.4.7-.8.9-1.2.1-.1.1-.2.2-.3.6-1.1.8-2.4.6-3.6l5.6-4.1zm-7.3 3.5.1.9c.1.9 0 1.8-.4 2.6l-6-6c.8-.4 1.7-.5 2.6-.4l.9.1L15 4.9 19.1 9l-4.9 3.6z"})),Lt=function(){return Lt=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&s[s.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!s||l[1]>s[0]&&l[1]0&&s[s.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!s||l[1]>s[0]&&l[1]0&&s[s.length-1])||6!==l[0]&&2!==l[0])){a=0;continue}if(3===l[0]&&(!s||l[1]>s[0]&&l[1]0?(0,s.jsx)("span",{className:"parsely-write-titles-text",children:(0,o.createInterpolateElement)( // translators: %1$s is the tone, %2$s is the persona. -(0,p.__)("We've generated a few titles based on the content of your post, written as a .","wp-parsely"),{tone:(0,i.jsx)("strong",{children:N(y)}),persona:(0,i.jsx)("strong",{children:x(w)})})}),F,L,I]}),void 0!==P&&D]})})},Qe=function(){return Qe=Object.assign||function(e){for(var t,n=1,r=arguments.length;n titles based on the content of your post, written as a .","wp-parsely"),{tone:(0,s.jsx)("strong",{children:xt(d)}),persona:(0,s.jsx)("strong",{children:vt(v)})})}):(0,c.__)("Use Parse.ly AI to generate a title for your post.","wp-parsely"),(0,s.jsxs)(i.Button,{href:"https://docs.parse.ly/plugin-content-helper/#h-title-suggestions-beta",target:"_blank",variant:"link",children:[(0,c.__)("Learn more about Parse.ly AI","wp-parsely"),(0,s.jsx)(V,{icon:D,size:18,className:"parsely-external-link-icon"})]})]}),l&&(0,s.jsx)(i.Notice,{status:"info",isDismissible:!1,className:"wp-parsely-content-helper-error",children:l.message}),void 0!==P&&(0,s.jsx)(Vt,{title:P,type:tt.PostTitle,isOriginal:!0}),00&&(0,s.jsx)(Dt,{pinnedTitles:b,isOpen:!0}),_.length>0&&(0,s.jsx)(Ht,{suggestions:_,isOpen:!0,isLoading:w})]}),(0,s.jsx)(Ft,{isLoading:w,onPersonaChange:function(e){O("Persona",e),y(e)},onSettingChange:O,onToneChange:function(e){O("Tone",e),f(e)},persona:t.TitleSuggestionsSettings.Persona,tone:t.TitleSuggestionsSettings.Tone}),(0,s.jsx)("div",{className:"title-suggestions-generate",children:(0,s.jsxs)(i.Button,{variant:"primary",isBusy:w,disabled:w||"custom"===d||"custom"===v,onClick:function(){return Wt(void 0,void 0,void 0,(function(){return Zt(this,(function(e){switch(e.label){case 0:return u(void 0),!1!==w?[3,2]:(g.trackEvent("title_suggestions_generate_pressed",{request_more:_.length>0,total_titles:_.length,total_pinned:_.filter((function(e){return e.isPinned})).length,tone:d,persona:v}),[4,(t=tt.PostTitle,n=A,r=d,s=v,Wt(void 0,void 0,void 0,(function(){var e,i,a;return Zt(this,(function(l){switch(l.label){case 0:return[4,E(!0)];case 1:l.sent(),e=new zt,l.label=2;case 2:return l.trys.push([2,5,,6]),[4,e.generateTitles(n,3,r,s)];case 3:return i=l.sent(),[4,T(t,i)];case 4:return l.sent(),[3,6];case 5:return a=l.sent(),u(a),T(t,[]),[3,6];case 6:return[4,E(!1)];case 7:return l.sent(),[2]}}))})))]);case 1:e.sent(),e.label=2;case 2:return[2]}var t,n,r,s}))}))},children:[w&&(0,c.__)("Generating Titles…","wp-parsely"),!w&&x.length>0&&(0,c.__)("Generate More","wp-parsely"),!w&&0===x.length&&(0,c.__)("Generate Titles","wp-parsely")]})})]})})},qt=function(){return qt=Object.assign||function(e){for(var t,n=1,r=arguments.length;n array('react', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-plugins', 'wp-url', 'wp-wordcount'), 'version' => 'ec0f278129ee227f1c61'); + array('react', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-plugins', 'wp-primitives', 'wp-url', 'wp-wordcount'), 'version' => '2d0071eca570bd4ce8c0'); diff --git a/build/content-helper/excerpt-generator.css b/build/content-helper/excerpt-generator.css index 9d05adffb..535190b0b 100644 --- a/build/content-helper/excerpt-generator.css +++ b/build/content-helper/excerpt-generator.css @@ -1,2 +1 @@ -.wp-parsely-beta-badge{align-self:end;background-color:var(--parsely-green);border-radius:.34em;box-shadow:0 .0625rem .1875rem rgba(0,0,0,.2);color:#fff;display:inline-block;font-size:.75rem;font-weight:700;margin:.125em .125em .125em auto;opacity:.8;padding:.15em .65em;text-transform:uppercase;-webkit-user-select:none;-moz-user-select:none;user-select:none}.parsely-tone-selector-dropdown{margin-bottom:.625rem;width:100%}.parsely-tone-selector-dropdown.is-disabled{opacity:.5;pointer-events:none}.parsely-tone-selector-dropdown .components-dropdown-menu__toggle{display:flex;gap:.625rem;width:100%}.parsely-tone-selector-dropdown .components-dropdown-menu__toggle svg:first-of-type path{transform:scale(1.4);transform-origin:center}.parsely-tone-selector-dropdown .parsely-tone-selector-label{flex-grow:2;text-align:left}.parsely-tone-selector-dropdown .parsely-tone-selector-label:first-letter{text-transform:uppercase}#wp-parsely-dashboard-widget,.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e}.parsely-persona-selector-dropdown{margin-bottom:.625rem;width:100%}.parsely-persona-selector-dropdown.is-disabled{opacity:.5;pointer-events:none}.parsely-persona-selector-dropdown .components-dropdown-menu__toggle{display:flex;gap:.625rem;width:100%}.parsely-persona-selector-dropdown .parsely-persona-selector-label{flex-grow:2;text-align:left}.parsely-persona-selector-dropdown .parsely-persona-selector-label:first-letter{text-transform:uppercase}.wp-parsely-popover button.components-button.components-menu-item__button.is-selected,.wp-parsely-popover button.components-button.components-menu-item__button.is-selected:focus{box-shadow:0 0 0 2px var(--parsely-green);outline:3px solid transparent} -#wp-parsely-dashboard-widget,.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e}.editor-post-excerpt__spinner{align-items:center;background:hsla(0,0%,100%,.7);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:1.25rem;z-index:10}.editor-post-excerpt__spinner.has-word-count{bottom:1.25rem}.wp-parsely-excerpt-generator{margin-top:.9375rem}.wp-parsely-excerpt-generator .wp-parsely-excerpt-generator-header{align-items:center;display:flex;justify-content:flex-start;margin-bottom:.625rem}.wp-parsely-excerpt-generator .wp-parsely-excerpt-generator-header .wp-parsely-excerpt-generator-header-label{display:inline-block;font-size:.6875rem;font-weight:500;line-height:1.4;margin-left:.3125rem;padding:0;text-transform:uppercase}.wp-parsely-excerpt-generator .wp-parsely-excerpt-generator-controls{align-items:center;display:flex;gap:.9375rem;justify-content:center}.wp-parsely-excerpt-generator .wp-parsely-excerpt-generator-error,.wp-parsely-excerpt-generator .wp-parsely-excerpt-generator-error p{margin:0} +#wp-parsely-dashboard-widget,.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"],.settings_page_parsely,.wp-parsely-content-helper,.wp-parsely-excerpt-generator,.wp-parsely-panel,.wp-parsely-popover{--base-font:"source-sans-pro",arial,sans-serif;--numeric-font:"ff-din-round-web",sans-serif;--parsely-green:#59a744;--parsely-green-10:#c7ecb1;--parsely-green-65:#2a691b;--gray-200:#f7f8f9;--gray-300:#edeeef;--gray-400:#d7dbdf;--gray-500:#959da5;--gray-600:#586069;--gray-700:#444d56;--gray-900:#24292e;--blue-500:#44a8e5;--blue-550:#2596db;--green-500:#7bc01b;--green-900:#3d6307;--ref-direct:205,13%,52%;--ref-internal:161,91%,41%;--ref-social:210,72%,41%;--ref-search:42,100%,50%;--ref-other:3,76%,58%;--base-text:var(--gray-900);--base-text-2:var(--gray-600);--base-3:var(--gray-400);--border:var(--gray-400);--data:var(--green-500);--control:var(--blue-500);--grid-unit-5:0.25rem;--grid-unit-10:0.5rem;--grid-unit-15:0.75rem;--grid-unit-20:1rem;--font-size--smaller:0.688rem;--font-size--small:0.75rem;--font-size--medium:0.875rem;--font-size--large:1rem;--font-size--extra-large:1.2rem;--black:#000;--sidebar-black:#1e1e1e;--sidebar-white:#f0f0f0}.editor-post-excerpt__loading_animation{left:.5625rem;position:absolute;top:1.8125rem}.editor-post-excerpt__textarea{margin-bottom:var(--grid-unit-10,.5rem)}.editor-post-excerpt__textarea textarea:-moz-read-only{background-color:var(--Gutenberg-White,#fff)}.editor-post-excerpt__textarea textarea:read-only{background-color:var(--Gutenberg-White,#fff)}.wp-parsely-excerpt-generator{margin-top:1.25rem}.wp-parsely-excerpt-generator .wp-parsely-excerpt-generator-header{align-items:center;display:flex;justify-content:flex-start}.wp-parsely-excerpt-generator .wp-parsely-excerpt-generator-header .wp-parsely-excerpt-generator-header-label{display:inline-block;font-size:.6875rem;font-weight:600;line-height:1.4;margin-left:.3125rem;padding:0;text-transform:uppercase}.wp-parsely-excerpt-generator .wp-parsely-excerpt-generator-header .wp-parsely-excerpt-generator-header-label span.beta-label{color:var(--Gutenberg-Gray-700,#757575);padding-left:.375rem}.wp-parsely-excerpt-generator .wp-parsely-excerpt-generator-controls{display:flex;gap:var(--grid-unit-10,.5rem)}.wp-parsely-excerpt-generator .wp-parsely-excerpt-generator-controls button{flex-grow:1;height:2.5rem;justify-content:center;margin-bottom:var(--grid-unit-10,.5rem);margin-top:var(--grid-unit-10,.5rem);padding:var(--grid-unit-10,.5rem) var(--grid-unit-15,.75rem)} diff --git a/build/content-helper/excerpt-generator.js b/build/content-helper/excerpt-generator.js index ae784b2bd..364b0f830 100644 --- a/build/content-helper/excerpt-generator.js +++ b/build/content-helper/excerpt-generator.js @@ -1,3 +1,3 @@ -!function(){"use strict";var e={251:function(e,t,r){var n=r(196),o=Symbol.for("react.element"),i=Symbol.for("react.fragment"),a=Object.prototype.hasOwnProperty,s=n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};function c(e,t,r){var n,i={},c=null,u=null;for(n in void 0!==r&&(c=""+r),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(u=t.ref),t)a.call(t,n)&&!l.hasOwnProperty(n)&&(i[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps)void 0===i[n]&&(i[n]=t[n]);return{$$typeof:o,type:e,key:c,ref:u,props:i,_owner:s.current}}t.Fragment=i,t.jsx=c,t.jsxs=c},893:function(e,t,r){e.exports=r(251)},196:function(e){e.exports=window.React}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var i=t[n]={exports:{}};return e[n](i,i.exports,r),i.exports}r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e,t,n=window.wp.hooks,o=window.wp.data,i=window.wp.plugins,a=r(893),s=window.wp.components,l=window.wp.editPost,c=window.wp.editor,u=window.wp.element,p=window.wp.i18n,d=window.wp.wordcount,y=function(){function e(){this._tkq=[],this.isLoaded=!1,this.isEnabled=!1,"undefined"!=typeof wpParselyTracksTelemetry&&(this.isEnabled=!0,this.loadTrackingLibrary())}return e.getInstance=function(){return window.wpParselyTelemetryInstance||Object.defineProperty(window,"wpParselyTelemetryInstance",{value:new e,writable:!1,configurable:!1,enumerable:!1}),window.wpParselyTelemetryInstance},e.prototype.loadTrackingLibrary=function(){var e=this,t=document.createElement("script");t.async=!0,t.src="//stats.wp.com/w.js",t.onload=function(){e.isLoaded=!0,e._tkq=window._tkq||[]},document.head.appendChild(t)},e.trackEvent=function(t,r){return void 0===r&&(r={}),n=this,o=void 0,a=function(){var n;return function(e,t){var r,n,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(s){return function(l){return function(s){if(r)throw new TypeError("Generator is already executing.");for(;i&&(i=0,s[0]&&(a=0)),a;)try{if(r=1,n&&(o=2&s[0]?n.return:s[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,s[1])).done)return o;switch(n=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return a.label++,{value:s[1],done:!1};case 5:a.label++,n=s[1],s=[0];continue;case 7:s=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=1e4&&(clearInterval(i),r("Telemetry library not loaded"))}),100);else r("Telemetry not enabled")}))},e.prototype.trackEvent=function(t,r){var n;this.isLoaded?(0!==t.indexOf(e.TRACKS_PREFIX)&&(t=e.TRACKS_PREFIX+t),this.isEventNameValid(t)?(r=this.prepareProperties(r),null===(n=this._tkq)||void 0===n||n.push(["recordEvent",t,r])):console.error("Error tracking event: Invalid event name")):console.error("Error tracking event: Telemetry not loaded")},e.prototype.isTelemetryEnabled=function(){return this.isEnabled},e.prototype.isProprietyValid=function(t){return e.PROPERTY_REGEX.test(t)},e.prototype.isEventNameValid=function(t){return e.EVENT_NAME_REGEX.test(t)},e.prototype.prepareProperties=function(e){return(e=this.sanitizeProperties(e)).parsely_version=wpParselyTracksTelemetry.version,wpParselyTracksTelemetry.user&&(e._ut=wpParselyTracksTelemetry.user.type,e._ui=wpParselyTracksTelemetry.user.id),wpParselyTracksTelemetry.vipgo_env&&(e.vipgo_env=wpParselyTracksTelemetry.vipgo_env),this.sanitizeProperties(e)},e.prototype.sanitizeProperties=function(e){var t=this,r={};return Object.keys(e).forEach((function(n){t.isProprietyValid(n)&&(r[n]=e[n])})),r},e.TRACKS_PREFIX="wpparsely_",e.EVENT_NAME_REGEX=/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/,e.PROPERTY_REGEX=/^[a-z_][a-z0-9_]*$/,e}(),f=(y.trackEvent,function(e){var t=e.text,r=void 0===t?(0,p.__)("Beta","wp-parsely"):t,n=e.color,o=void 0===n?"var(--parsely-green)":n,i=e.fontSize,s={backgroundColor:o,fontSize:void 0===i?"0.75rem":i};return(0,a.jsx)("div",{className:"wp-parsely-beta-badge",style:s,children:r})}),h=function(e){var t=e.size,r=void 0===t?24:t;return(0,a.jsxs)(s.SVG,{height:r,viewBox:"0 0 60 65",width:r,xmlns:"http://www.w3.org/2000/svg",children:[(0,a.jsx)(s.Path,{fill:"#5ba745",d:"M23.72,51.53c0-.18,0-.34-.06-.52a13.11,13.11,0,0,0-2.1-5.53A14.74,14.74,0,0,0,19.12,43c-.27-.21-.5-.11-.51.22l-.24,3.42c0,.33-.38.35-.49,0l-1.5-4.8a1.4,1.4,0,0,0-.77-.78,23.91,23.91,0,0,0-3.1-.84c-1.38-.24-3.39-.39-3.39-.39-.34,0-.45.21-.25.49l2.06,3.76c.2.27,0,.54-.29.33l-4.51-3.6a3.68,3.68,0,0,0-2.86-.48c-1,.16-2.44.46-2.44.46a.68.68,0,0,0-.39.25.73.73,0,0,0-.14.45S.41,43,.54,44a3.63,3.63,0,0,0,1.25,2.62L6.48,50c.28.2.09.49-.23.37l-4.18-.94c-.32-.12-.5,0-.4.37,0,0,.69,1.89,1.31,3.16a24,24,0,0,0,1.66,2.74,1.34,1.34,0,0,0,1,.52l5,.13c.33,0,.41.38.1.48L7.51,58c-.31.1-.34.35-.07.55a14.29,14.29,0,0,0,3.05,1.66,13.09,13.09,0,0,0,5.9.5,25.13,25.13,0,0,0,4.34-1,9.55,9.55,0,0,1-.08-1.2,9.32,9.32,0,0,1,3.07-6.91"}),(0,a.jsx)(s.Path,{fill:"#5ba745",d:"M59.7,41.53a.73.73,0,0,0-.14-.45.68.68,0,0,0-.39-.25s-1.43-.3-2.44-.46a3.64,3.64,0,0,0-2.86.48l-4.51,3.6c-.26.21-.49-.06-.29-.33l2.06-3.76c.2-.28.09-.49-.25-.49,0,0-2,.15-3.39.39a23.91,23.91,0,0,0-3.1.84,1.4,1.4,0,0,0-.77.78l-1.5,4.8c-.11.32-.48.3-.49,0l-.24-3.42c0-.33-.24-.43-.51-.22a14.74,14.74,0,0,0-2.44,2.47A13.11,13.11,0,0,0,36.34,51c0,.18,0,.34-.06.52a9.26,9.26,0,0,1,3,8.1,24.1,24.1,0,0,0,4.34,1,13.09,13.09,0,0,0,5.9-.5,14.29,14.29,0,0,0,3.05-1.66c.27-.2.24-.45-.07-.55l-3.22-1.17c-.31-.1-.23-.47.1-.48l5-.13a1.38,1.38,0,0,0,1-.52A24.6,24.6,0,0,0,57,52.92c.61-1.27,1.31-3.16,1.31-3.16.1-.33-.08-.49-.4-.37l-4.18.94c-.32.12-.51-.17-.23-.37l4.69-3.34A3.63,3.63,0,0,0,59.46,44c.13-1,.24-2.47.24-2.47"}),(0,a.jsx)(s.Path,{fill:"#5ba745",d:"M46.5,25.61c0-.53-.35-.72-.8-.43l-4.86,2.66c-.45.28-.56-.27-.23-.69l4.66-6.23a2,2,0,0,0,.28-1.68,36.51,36.51,0,0,0-2.19-4.89,34,34,0,0,0-2.81-3.94c-.33-.41-.74-.35-.91.16l-2.28,5.68c-.16.5-.6.48-.59-.05l.28-8.93a2.54,2.54,0,0,0-.66-1.64S35,4.27,33.88,3.27,30.78.69,30.78.69a1.29,1.29,0,0,0-1.54,0s-1.88,1.49-3.12,2.59-2.48,2.35-2.48,2.35A2.5,2.5,0,0,0,23,7.27l.27,8.93c0,.53-.41.55-.58.05l-2.29-5.69c-.17-.5-.57-.56-.91-.14a35.77,35.77,0,0,0-3,4.2,35.55,35.55,0,0,0-2,4.62,2,2,0,0,0,.27,1.67l4.67,6.24c.33.42.23,1-.22.69l-4.87-2.66c-.45-.29-.82-.1-.82.43a18.6,18.6,0,0,0,.83,5.07,20.16,20.16,0,0,0,5.37,7.77c3.19,3,5.93,7.8,7.45,11.08A9.6,9.6,0,0,1,30,49.09a9.31,9.31,0,0,1,2.86.45c1.52-3.28,4.26-8.11,7.44-11.09a20.46,20.46,0,0,0,5.09-7,19,19,0,0,0,1.11-5.82"}),(0,a.jsx)(s.Path,{fill:"#5ba745",d:"M36.12,58.44A6.12,6.12,0,1,1,30,52.32a6.11,6.11,0,0,1,6.12,6.12"})]})},w=window.wp.apiFetch,v=r.n(w),_=window.wp.url,b=function(e){void 0===e&&(e=null);var t="";(null==e?void 0:e.children)&&(t=e.children);var r="content-helper-error-message";return(null==e?void 0:e.className)&&(r+=" "+e.className),(0,a.jsx)("div",{className:r,"data-testid":null==e?void 0:e.testId,dangerouslySetInnerHTML:{__html:t}})},g=(e=function(t,r){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])},e(t,r)},function(t,r){if("function"!=typeof r&&null!==r)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");function __(){this.constructor=t}e(t,r),t.prototype=null===r?Object.create(r):(__.prototype=r.prototype,new __)});!function(e){e.CannotFormulateApiQuery="ch_cannot_formulate_api_query",e.FetchError="fetch_error",e.HttpRequestFailed="http_request_failed",e[e.ParselyApiForbidden=403]="ParselyApiForbidden",e.ParselyApiResponseContainsError="ch_response_contains_error",e.ParselyApiReturnedNoData="ch_parsely_api_returned_no_data",e.ParselyApiReturnedTooManyResults="ch_parsely_api_returned_too_many_results",e[e.ParselyApiUnauthorized=401]="ParselyApiUnauthorized",e.PluginCredentialsNotSetMessageDetected="parsely_credentials_not_set_message_detected",e.PluginSettingsApiSecretNotSet="parsely_api_secret_not_set",e.PluginSettingsSiteIdNotSet="parsely_site_id_not_set",e.PostIsNotPublished="ch_post_not_published"}(t||(t={}));var m=function(e){function r(n,o,i){void 0===i&&(i=(0,p.__)("Error: ","wp-parsely"));var a=e.call(this,i+n)||this;a.hint=null,a.name=a.constructor.name,a.code=o;var s=[t.ParselyApiForbidden,t.ParselyApiResponseContainsError,t.ParselyApiReturnedNoData,t.ParselyApiReturnedTooManyResults,t.ParselyApiUnauthorized,t.PluginCredentialsNotSetMessageDetected,t.PluginSettingsApiSecretNotSet,t.PluginSettingsSiteIdNotSet,t.PostIsNotPublished];return a.retryFetch=!s.includes(a.code),Object.setPrototypeOf(a,r.prototype),a}return g(r,e),r.prototype.Message=function(e){return void 0===e&&(e=null),[t.PluginCredentialsNotSetMessageDetected,t.PluginSettingsSiteIdNotSet,t.PluginSettingsApiSecretNotSet].includes(this.code)?function(e){return void 0===e&&(e=null),(0,a.jsx)(b,{className:null==e?void 0:e.className,testId:"empty-credentials-message",children:window.wpParselyEmptyCredentialsMessage})}(e):(this.code===t.FetchError&&(this.hint=this.Hint((0,p.__)("This error can sometimes be caused by ad-blockers or browser tracking protections. Please add this site to any applicable allow lists and try again.","wp-parsely"))),this.code===t.ParselyApiForbidden&&(this.hint=this.Hint((0,p.__)("Please ensure that the Site ID and API Secret given in the plugin's settings are correct.","wp-parsely"))),this.code===t.HttpRequestFailed&&(this.hint=this.Hint((0,p.__)("The Parse.ly API cannot be reached. Please verify that you are online.","wp-parsely"))),this.code===t.ParselyApiUnauthorized&&(this.message=(0,p.__)("This feature is accessible to select customers participating in its beta testing.","wp-parsely")),(0,a.jsx)(b,{className:null==e?void 0:e.className,testId:"error",children:"

".concat(this.message,"

").concat(this.hint?this.hint:"")}))},r.prototype.Hint=function(e){return'

'.concat((0,p.__)("Hint:","wp-parsely")," ").concat(e,"

")},r}(Error),x=function(){function e(){}return e.prototype.generateExcerpt=function(e,r){var n,o,i,a,s;return o=this,i=void 0,s=function(){var o,i;return function(e,t){var r,n,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(s){return function(l){return function(s){if(r)throw new TypeError("Generator is already executing.");for(;i&&(i=0,s[0]&&(a=0)),a;)try{if(r=1,n&&(o=2&s[0]?n.return:s[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,s[1])).done)return o;switch(n=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return a.label++,{value:s[1],done:!1};case 5:a.label++,n=s[1],s=[0];continue;case 7:s=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0,N=(0,d.count)(i||k,"words",{}),A=(0,p.sprintf)( +!function(){"use strict";var e={251:function(e,t,r){var n=r(196),a=Symbol.for("react.element"),s=Symbol.for("react.fragment"),o=Object.prototype.hasOwnProperty,i=n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};function c(e,t,r){var n,s={},c=null,p=null;for(n in void 0!==r&&(c=""+r),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(p=t.ref),t)o.call(t,n)&&!l.hasOwnProperty(n)&&(s[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps)void 0===s[n]&&(s[n]=t[n]);return{$$typeof:a,type:e,key:c,ref:p,props:s,_owner:i.current}}t.Fragment=s,t.jsx=c,t.jsxs=c},893:function(e,t,r){e.exports=r(251)},196:function(e){e.exports=window.React}},t={};function r(n){var a=t[n];if(void 0!==a)return a.exports;var s=t[n]={exports:{}};return e[n](s,s.exports,r),s.exports}r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e,t,n=window.wp.hooks,a=window.wp.data,s=window.wp.plugins,o=r(893),i=window.wp.components,l=window.wp.editPost,c=window.wp.editor,p=window.wp.element,u=window.wp.i18n,d=window.wp.wordcount,y=r(196),h=window.wp.primitives,f=(0,y.createElement)(h.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,y.createElement)(h.Path,{d:"M19.5 4.5h-7V6h4.44l-5.97 5.97 1.06 1.06L18 7.06v4.44h1.5v-7Zm-13 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-3H17v3a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h3V5.5h-3Z"})),w=function(){function e(){this._tkq=[],this.isLoaded=!1,this.isEnabled=!1,"undefined"!=typeof wpParselyTracksTelemetry&&(this.isEnabled=!0,this.loadTrackingLibrary())}return e.getInstance=function(){return window.wpParselyTelemetryInstance||Object.defineProperty(window,"wpParselyTelemetryInstance",{value:new e,writable:!1,configurable:!1,enumerable:!1}),window.wpParselyTelemetryInstance},e.prototype.loadTrackingLibrary=function(){var e=this,t=document.createElement("script");t.async=!0,t.src="//stats.wp.com/w.js",t.onload=function(){e.isLoaded=!0,e._tkq=window._tkq||[]},document.head.appendChild(t)},e.trackEvent=function(t){return r=this,n=arguments,s=function(t,r){var n;return void 0===r&&(r={}),function(e,t){var r,n,a,s,o={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]};return s={next:i(0),throw:i(1),return:i(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function i(i){return function(l){return function(i){if(r)throw new TypeError("Generator is already executing.");for(;s&&(s=0,i[0]&&(o=0)),o;)try{if(r=1,n&&(a=2&i[0]?n.return:i[0]?n.throw||((a=n.return)&&a.call(n),0):n.next)&&!(a=a.call(n,i[1])).done)return a;switch(n=0,a&&(i=[2&i[0],a.value]),i[0]){case 0:case 1:a=i;break;case 4:return o.label++,{value:i[1],done:!1};case 5:o.label++,n=i[1],i=[0];continue;case 7:i=o.ops.pop(),o.trys.pop();continue;default:if(!((a=(a=o.trys).length>0&&a[a.length-1])||6!==i[0]&&2!==i[0])){o=0;continue}if(3===i[0]&&(!a||i[1]>a[0]&&i[1]=1e4&&(clearInterval(s),r("Telemetry library not loaded"))}),100);else r("Telemetry not enabled")}))},e.prototype.trackEvent=function(t,r){var n;this.isLoaded?(0!==t.indexOf(e.TRACKS_PREFIX)&&(t=e.TRACKS_PREFIX+t),this.isEventNameValid(t)?(r=this.prepareProperties(r),null===(n=this._tkq)||void 0===n||n.push(["recordEvent",t,r])):console.error("Error tracking event: Invalid event name")):console.error("Error tracking event: Telemetry not loaded")},e.prototype.isTelemetryEnabled=function(){return this.isEnabled},e.prototype.isProprietyValid=function(t){return e.PROPERTY_REGEX.test(t)},e.prototype.isEventNameValid=function(t){return e.EVENT_NAME_REGEX.test(t)},e.prototype.prepareProperties=function(e){return(e=this.sanitizeProperties(e)).parsely_version=wpParselyTracksTelemetry.version,wpParselyTracksTelemetry.user&&(e._ut=wpParselyTracksTelemetry.user.type,e._ui=wpParselyTracksTelemetry.user.id),wpParselyTracksTelemetry.vipgo_env&&(e.vipgo_env=wpParselyTracksTelemetry.vipgo_env),this.sanitizeProperties(e)},e.prototype.sanitizeProperties=function(e){var t=this,r={};return Object.keys(e).forEach((function(n){t.isProprietyValid(n)&&(r[n]=e[n])})),r},e.TRACKS_PREFIX="wpparsely_",e.EVENT_NAME_REGEX=/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/,e.PROPERTY_REGEX=/^[a-z_][a-z0-9_]*$/,e}(),v=(w.trackEvent,function(e){var t=e.size,r=void 0===t?24:t,n=e.className,a=void 0===n?"wp-parsely-icon":n;return(0,o.jsxs)(i.SVG,{className:a,height:r,viewBox:"0 0 60 65",width:r,xmlns:"http://www.w3.org/2000/svg",children:[(0,o.jsx)(i.Path,{fill:"#5ba745",d:"M23.72,51.53c0-.18,0-.34-.06-.52a13.11,13.11,0,0,0-2.1-5.53A14.74,14.74,0,0,0,19.12,43c-.27-.21-.5-.11-.51.22l-.24,3.42c0,.33-.38.35-.49,0l-1.5-4.8a1.4,1.4,0,0,0-.77-.78,23.91,23.91,0,0,0-3.1-.84c-1.38-.24-3.39-.39-3.39-.39-.34,0-.45.21-.25.49l2.06,3.76c.2.27,0,.54-.29.33l-4.51-3.6a3.68,3.68,0,0,0-2.86-.48c-1,.16-2.44.46-2.44.46a.68.68,0,0,0-.39.25.73.73,0,0,0-.14.45S.41,43,.54,44a3.63,3.63,0,0,0,1.25,2.62L6.48,50c.28.2.09.49-.23.37l-4.18-.94c-.32-.12-.5,0-.4.37,0,0,.69,1.89,1.31,3.16a24,24,0,0,0,1.66,2.74,1.34,1.34,0,0,0,1,.52l5,.13c.33,0,.41.38.1.48L7.51,58c-.31.1-.34.35-.07.55a14.29,14.29,0,0,0,3.05,1.66,13.09,13.09,0,0,0,5.9.5,25.13,25.13,0,0,0,4.34-1,9.55,9.55,0,0,1-.08-1.2,9.32,9.32,0,0,1,3.07-6.91"}),(0,o.jsx)(i.Path,{fill:"#5ba745",d:"M59.7,41.53a.73.73,0,0,0-.14-.45.68.68,0,0,0-.39-.25s-1.43-.3-2.44-.46a3.64,3.64,0,0,0-2.86.48l-4.51,3.6c-.26.21-.49-.06-.29-.33l2.06-3.76c.2-.28.09-.49-.25-.49,0,0-2,.15-3.39.39a23.91,23.91,0,0,0-3.1.84,1.4,1.4,0,0,0-.77.78l-1.5,4.8c-.11.32-.48.3-.49,0l-.24-3.42c0-.33-.24-.43-.51-.22a14.74,14.74,0,0,0-2.44,2.47A13.11,13.11,0,0,0,36.34,51c0,.18,0,.34-.06.52a9.26,9.26,0,0,1,3,8.1,24.1,24.1,0,0,0,4.34,1,13.09,13.09,0,0,0,5.9-.5,14.29,14.29,0,0,0,3.05-1.66c.27-.2.24-.45-.07-.55l-3.22-1.17c-.31-.1-.23-.47.1-.48l5-.13a1.38,1.38,0,0,0,1-.52A24.6,24.6,0,0,0,57,52.92c.61-1.27,1.31-3.16,1.31-3.16.1-.33-.08-.49-.4-.37l-4.18.94c-.32.12-.51-.17-.23-.37l4.69-3.34A3.63,3.63,0,0,0,59.46,44c.13-1,.24-2.47.24-2.47"}),(0,o.jsx)(i.Path,{fill:"#5ba745",d:"M46.5,25.61c0-.53-.35-.72-.8-.43l-4.86,2.66c-.45.28-.56-.27-.23-.69l4.66-6.23a2,2,0,0,0,.28-1.68,36.51,36.51,0,0,0-2.19-4.89,34,34,0,0,0-2.81-3.94c-.33-.41-.74-.35-.91.16l-2.28,5.68c-.16.5-.6.48-.59-.05l.28-8.93a2.54,2.54,0,0,0-.66-1.64S35,4.27,33.88,3.27,30.78.69,30.78.69a1.29,1.29,0,0,0-1.54,0s-1.88,1.49-3.12,2.59-2.48,2.35-2.48,2.35A2.5,2.5,0,0,0,23,7.27l.27,8.93c0,.53-.41.55-.58.05l-2.29-5.69c-.17-.5-.57-.56-.91-.14a35.77,35.77,0,0,0-3,4.2,35.55,35.55,0,0,0-2,4.62,2,2,0,0,0,.27,1.67l4.67,6.24c.33.42.23,1-.22.69l-4.87-2.66c-.45-.29-.82-.1-.82.43a18.6,18.6,0,0,0,.83,5.07,20.16,20.16,0,0,0,5.37,7.77c3.19,3,5.93,7.8,7.45,11.08A9.6,9.6,0,0,1,30,49.09a9.31,9.31,0,0,1,2.86.45c1.52-3.28,4.26-8.11,7.44-11.09a20.46,20.46,0,0,0,5.09-7,19,19,0,0,0,1.11-5.82"}),(0,o.jsx)(i.Path,{fill:"#5ba745",d:"M36.12,58.44A6.12,6.12,0,1,1,30,52.32a6.11,6.11,0,0,1,6.12,6.12"})]})}),_=window.wp.apiFetch,m=r.n(_),g=window.wp.url,P=function(e){void 0===e&&(e=null);var t="";(null==e?void 0:e.children)&&(t=e.children);var r="content-helper-error-message";return(null==e?void 0:e.className)&&(r+=" "+e.className),(0,o.jsx)("div",{className:r,"data-testid":null==e?void 0:e.testId,dangerouslySetInnerHTML:{__html:t}})},b=(e=function(t,r){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])},e(t,r)},function(t,r){if("function"!=typeof r&&null!==r)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");function __(){this.constructor=t}e(t,r),t.prototype=null===r?Object.create(r):(__.prototype=r.prototype,new __)});!function(e){e.CannotFormulateApiQuery="ch_cannot_formulate_api_query",e.FetchError="fetch_error",e.HttpRequestFailed="http_request_failed",e[e.ParselyApiForbidden=403]="ParselyApiForbidden",e.ParselyApiResponseContainsError="ch_response_contains_error",e.ParselyApiReturnedNoData="ch_parsely_api_returned_no_data",e.ParselyApiReturnedTooManyResults="ch_parsely_api_returned_too_many_results",e[e.ParselyApiUnauthorized=401]="ParselyApiUnauthorized",e[e.ParselyInternalServerError=500]="ParselyInternalServerError",e[e.ParselySchemaValidationFailed=422]="ParselySchemaValidationFailed",e[e.ParselyUpstreamMalformedResponse=507]="ParselyUpstreamMalformedResponse",e[e.ParselyUpstreamNotAvailable=503]="ParselyUpstreamNotAvailable",e.PluginCredentialsNotSetMessageDetected="parsely_credentials_not_set_message_detected",e.PluginSettingsApiSecretNotSet="parsely_api_secret_not_set",e.PluginSettingsSiteIdNotSet="parsely_site_id_not_set",e.PostIsNotPublished="ch_post_not_published"}(t||(t={}));var x=function(e){function r(n,a,s){void 0===s&&(s=(0,u.__)("Error: ","wp-parsely"));var o=e.call(this,s+n)||this;o.hint=null,o.name=o.constructor.name,o.code=a;var i=[t.ParselyApiForbidden,t.ParselyApiResponseContainsError,t.ParselyApiReturnedNoData,t.ParselyApiReturnedTooManyResults,t.ParselyApiUnauthorized,t.PluginCredentialsNotSetMessageDetected,t.PluginSettingsApiSecretNotSet,t.PluginSettingsSiteIdNotSet,t.PostIsNotPublished];return o.retryFetch=!i.includes(o.code),Object.setPrototypeOf(o,r.prototype),o.code===t.ParselyApiUnauthorized?o.message=(0,u.__)('This AI-powered feature is opt-in. To gain access, please submit a request here.',"wp-parsely"):o.code===t.ParselyInternalServerError?o.message=(0,u.__)("The Parse.ly API returned an internal server error. Please try again later.","wp-parsely"):o.code===t.HttpRequestFailed&&o.message.includes("cURL error 28")?o.message=(0,u.__)("The Parse.ly API did not respond in a timely manner. Please try again later.","wp-parsely"):o.code===t.ParselySchemaValidationFailed?o.message=(0,u.__)("The Parse.ly API returned a validation error. Please try again later.","wp-parsely"):o.code===t.ParselyUpstreamMalformedResponse&&o.message.includes("Insufficient Storage")?o.message=(0,u.__)("The Parse.ly API couldn't find any relevant data to fulfill the request. Please retry with a different input.","wp-parsely"):o.code===t.ParselyUpstreamMalformedResponse?o.message=(0,u.__)("The Parse.ly API returned a malformed response. Please try again later.","wp-parsely"):o.code===t.ParselyUpstreamNotAvailable&&(o.message=(0,u.__)("The Parse.ly API is currently unavailable. Please try again later.","wp-parsely")),o}return b(r,e),r.prototype.Message=function(e){return void 0===e&&(e=null),[t.PluginCredentialsNotSetMessageDetected,t.PluginSettingsSiteIdNotSet,t.PluginSettingsApiSecretNotSet].includes(this.code)?function(e){return void 0===e&&(e=null),(0,o.jsx)(P,{className:null==e?void 0:e.className,testId:"empty-credentials-message",children:window.wpParselyEmptyCredentialsMessage})}(e):(this.code===t.FetchError&&(this.hint=this.Hint((0,u.__)("This error can sometimes be caused by ad-blockers or browser tracking protections. Please add this site to any applicable allow lists and try again.","wp-parsely"))),this.code===t.ParselyApiForbidden&&(this.hint=this.Hint((0,u.__)("Please ensure that the Site ID and API Secret given in the plugin's settings are correct.","wp-parsely"))),this.code===t.HttpRequestFailed&&(this.hint=this.Hint((0,u.__)("The Parse.ly API cannot be reached. Please verify that you are online.","wp-parsely"))),(0,o.jsx)(P,{className:null==e?void 0:e.className,testId:"error",children:"

".concat(this.message,"

").concat(this.hint?this.hint:"")}))},r.prototype.Hint=function(e){return'

'.concat((0,u.__)("Hint:","wp-parsely")," ").concat(e,"

")},r}(Error),E=function(){function e(){}return e.prototype.generateExcerpt=function(e,r){return n=this,a=void 0,o=function(){var n,a,s;return function(e,t){var r,n,a,s,o={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]};return s={next:i(0),throw:i(1),return:i(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function i(i){return function(l){return function(i){if(r)throw new TypeError("Generator is already executing.");for(;s&&(s=0,i[0]&&(o=0)),o;)try{if(r=1,n&&(a=2&i[0]?n.return:i[0]?n.throw||((a=n.return)&&a.call(n),0):n.next)&&!(a=a.call(n,i[1])).done)return a;switch(n=0,a&&(i=[2&i[0],a.value]),i[0]){case 0:case 1:a=i;break;case 4:return o.label++,{value:i[1],done:!1};case 5:o.label++,n=i[1],i=[0];continue;case 7:i=o.ops.pop(),o.trys.pop();continue;default:if(!((a=(a=o.trys).length>0&&a[a.length-1])||6!==i[0]&&2!==i[0])){o=0;continue}if(3===i[0]&&(!a||i[1]>a[0]&&i[1]0&&a[a.length-1])||6!==i[0]&&2!==i[0])){o=0;continue}if(3===i[0]&&(!a||i[1]>a[0]&&i[1]0,O=(0,d.count)(s||N,"words",{}),C=(0,u.sprintf)( // Translators: %1$s the number of words in the excerpt. -(0,p._n)("%1$s word","%1$s words",N,"wp-parsely"),N);return(0,u.useEffect)((function(){var e=document.querySelector(".editor-post-excerpt textarea");e&&(e.scrollTop=0)}),[i]),(0,a.jsxs)("div",{className:"editor-post-excerpt",children:[(0,a.jsxs)("div",{style:{position:"relative"},children:[t&&(0,a.jsx)("div",{className:"editor-post-excerpt__spinner"+(N>0?" has-word-count":""),children:(0,a.jsx)(s.Spinner,{})}),(0,a.jsx)(s.TextareaControl,{__nextHasNoMarginBottom:!0,label:(0,p.__)("Write an excerpt (optional)","wp-parsely"),className:"editor-post-excerpt__textarea",onChange:function(e){return b({excerpt:e})},disabled:t||j,value:j?i:k,help:N?A:null})]}),(0,a.jsx)(s.ExternalLink,{href:(0,p.__)("https://wordpress.org/documentation/article/page-post-settings-sidebar/#excerpt","wp-parsely"),children:(0,p.__)("Learn more about manual excerpts","wp-parsely")}),(0,a.jsxs)("div",{className:"wp-parsely-excerpt-generator",children:[(0,a.jsxs)("div",{className:"wp-parsely-excerpt-generator-header",children:[(0,a.jsx)(h,{size:20}),(0,a.jsx)("div",{className:"wp-parsely-excerpt-generator-header-label",children:(0,p.__)("Parse.ly AI","wp-parsely")}),(0,a.jsx)(f,{})]}),v&&(0,a.jsx)(s.Notice,{status:"info",isDismissible:!1,className:"wp-parsely-excerpt-generator-error",children:v.Message()}),(0,a.jsx)("div",{className:"wp-parsely-excerpt-generator-controls",children:j?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(s.Button,{variant:"secondary",onClick:function(){return P(void 0,void 0,void 0,(function(){return E(this,(function(e){switch(e.label){case 0:return[4,b({excerpt:i})];case 1:return e.sent(),l(""),y.trackEvent("excerpt_generator_accepted"),[2]}}))}))},children:(0,p.__)("Accept","wp-parsely")}),(0,a.jsx)(s.Button,{isDestructive:!0,variant:"secondary",onClick:function(){return P(void 0,void 0,void 0,(function(){return E(this,(function(e){return l(""),y.trackEvent("excerpt_generator_discarded"),[2]}))}))},children:(0,p.__)("Discard","wp-parsely")})]}):(0,a.jsx)(s.Button,{onClick:function(){return P(void 0,void 0,void 0,(function(){var e,t;return E(this,(function(n){switch(n.label){case 0:r(!0),_(void 0),n.label=1;case 1:return n.trys.push([1,3,4,5]),y.trackEvent("excerpt_generator_pressed"),[4,g.generateExcerpt(T,S)];case 2:return e=n.sent(),l(e),[3,5];case 3:return t=n.sent(),_(t),[3,5];case 4:return r(!1),[7];case 5:return[2]}}))}))},variant:"primary",isBusy:t,disabled:t,children:t?(0,p.__)("Generating…","wp-parsely"):(0,p.__)("Generate Excerpt","wp-parsely")})})]})]})},S=function(){return(0,a.jsx)(c.PostTypeSupportCheck,{supportKeys:"excerpt",children:(0,a.jsx)(l.PluginDocumentSettingPanel,{name:"parsely-post-excerpt",title:"Excerpt",children:(0,a.jsx)(k,{})})})};(0,n.addFilter)("plugins.registerPlugin","wp-parsely-excerpt-generator",(function(e,t){var r,a;return"wp-parsely-block-editor-sidebar"!==t||((null===(r=null===window||void 0===window?void 0:window.Jetpack_Editor_Initial_State)||void 0===r?void 0:r.available_blocks["ai-content-lens"])&&(console.log("Parse.ly: Jetpack AI is enabled and will be disabled."),(0,n.removeFilter)("blocks.registerBlockType","jetpack/ai-content-lens-features")),(0,i.registerPlugin)("wp-parsely-excerpt-generator",{render:S}),null===(a=(0,o.dispatch)("core/edit-post"))||void 0===a||a.removeEditorPanel("post-excerpt")),e}),1e3)}()}(); \ No newline at end of file +(0,u._n)("%1$s word","%1$s words",O,"wp-parsely"),O);return(0,p.useEffect)((function(){var e=document.querySelector(".editor-post-excerpt textarea");e&&(e.scrollTop=0)}),[s]),(0,o.jsxs)("div",{className:"editor-post-excerpt",children:[(0,o.jsxs)("div",{style:{position:"relative"},children:[t&&(0,o.jsx)("div",{className:"editor-post-excerpt__loading_animation",children:(0,o.jsx)(A,{})}),(0,o.jsx)(i.TextareaControl,{__nextHasNoMarginBottom:!0,label:(0,u.__)("Write an excerpt (optional)","wp-parsely"),className:"editor-post-excerpt__textarea",onChange:function(e){return b({excerpt:e})},readOnly:t||R,value:t?"":R?s:N,help:O?C:null})]}),(0,o.jsxs)(i.Button,{href:(0,u.__)("https://wordpress.org/documentation/article/page-post-settings-sidebar/#excerpt","wp-parsely"),target:"_blank",variant:"link",children:[(0,u.__)("Learn more about manual excerpts","wp-parsely"),(0,o.jsx)(i.Icon,{icon:f,size:18,className:"parsely-external-link-icon"})]}),(0,o.jsxs)("div",{className:"wp-parsely-excerpt-generator",children:[(0,o.jsxs)("div",{className:"wp-parsely-excerpt-generator-header",children:[(0,o.jsx)(v,{size:16}),(0,o.jsxs)("div",{className:"wp-parsely-excerpt-generator-header-label",children:[(0,u.__)("Generate With Parse.ly","wp-parsely"),(0,o.jsx)("span",{className:"beta-label",children:(0,u.__)("Beta","wp-parsely")})]})]}),g&&(0,o.jsx)(i.Notice,{status:"info",isDismissible:!1,className:"wp-parsely-excerpt-generator-error",children:g.Message()}),(0,o.jsx)("div",{className:"wp-parsely-excerpt-generator-controls",children:R?(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(i.Button,{variant:"secondary",onClick:function(){return S(void 0,void 0,void 0,(function(){return k(this,(function(e){switch(e.label){case 0:return[4,b({excerpt:s})];case 1:return e.sent(),l(""),w.trackEvent("excerpt_generator_accepted"),[2]}}))}))},children:(0,u.__)("Accept","wp-parsely")}),(0,o.jsx)(i.Button,{isDestructive:!0,variant:"secondary",onClick:function(){return S(void 0,void 0,void 0,(function(){return k(this,(function(e){return l(""),w.trackEvent("excerpt_generator_discarded"),[2]}))}))},children:(0,u.__)("Discard","wp-parsely")})]}):(0,o.jsxs)(i.Button,{onClick:function(){return S(void 0,void 0,void 0,(function(){var e,t;return k(this,(function(n){switch(n.label){case 0:r(!0),P(void 0),_(h+1),n.label=1;case 1:return n.trys.push([1,3,4,5]),w.trackEvent("excerpt_generator_pressed"),[4,x.generateExcerpt(I,j)];case 2:return e=n.sent(),l(e),[3,5];case 3:return t=n.sent(),P(t),[3,5];case 4:return r(!1),[7];case 5:return[2]}}))}))},variant:"primary",isBusy:t,disabled:t,children:[t&&(0,u.__)("Generating Excerpt…","wp-parsely"),!t&&h>0&&(0,u.__)("Regenerate Excerpt","wp-parsely"),!t&&0===h&&(0,u.__)("Generate Excerpt","wp-parsely")]})}),(0,o.jsxs)(i.Button,{href:"https://docs.parse.ly/plugin-content-helper/#h-excerpt-generator-beta",target:"_blank",variant:"link",children:[(0,u.__)("Learn more about Parse.ly AI","wp-parsely"),(0,o.jsx)(i.Icon,{icon:f,size:18,className:"parsely-external-link-icon"})]})]})]})},A=function(){return(0,o.jsx)(i.Animate,{type:"loading",children:function(e){var t=e.className;return(0,o.jsx)("span",{className:t,children:(0,u.__)("Generating…","wp-parsely")})}})},N=function(){return(0,o.jsx)(c.PostTypeSupportCheck,{supportKeys:"excerpt",children:(0,o.jsx)(l.PluginDocumentSettingPanel,{name:"parsely-post-excerpt",title:"Excerpt",children:(0,o.jsx)(T,{})})})};(0,n.addFilter)("plugins.registerPlugin","wp-parsely-excerpt-generator",(function(e,t){var r,o;return"wp-parsely-block-editor-sidebar"!==t||((null===(r=null===window||void 0===window?void 0:window.Jetpack_Editor_Initial_State)||void 0===r?void 0:r.available_blocks["ai-content-lens"])&&(console.log("Parse.ly: Jetpack AI is enabled and will be disabled."),(0,n.removeFilter)("blocks.registerBlockType","jetpack/ai-content-lens-features")),(0,s.registerPlugin)("wp-parsely-excerpt-generator",{render:N}),null===(o=(0,a.dispatch)("core/edit-post"))||void 0===o||o.removeEditorPanel("post-excerpt")),e}),1e3)}()}(); \ No newline at end of file diff --git a/build/loader.asset.php b/build/loader.asset.php index d54e6245d..7cab08fbc 100644 --- a/build/loader.asset.php +++ b/build/loader.asset.php @@ -1 +1 @@ - array('wp-hooks', 'wp-i18n'), 'version' => 'f5c2d06f6755fda3f6d4'); + array('wp-hooks', 'wp-i18n'), 'version' => '71d37502d12f3838b80d'); diff --git a/build/loader.js b/build/loader.js index 2ea8d95fa..b1ae47872 100644 --- a/build/loader.js +++ b/build/loader.js @@ -1 +1 @@ -!function(){"use strict";var n;n=window.wp.hooks,window.wpParselyHooks=(0,n.createHooks)(),function(){var n=function(){var n;return null===(n=window.wpParselyHooks)||void 0===n?void 0:n.doAction("wpParselyOnLoad")},o=function(){var n;return null===(n=window.wpParselyHooks)||void 0===n?void 0:n.doAction("wpParselyOnReady")};if("object"==typeof window.PARSELY){if("function"!=typeof window.PARSELY.onload)window.PARSELY.onload=n;else{var e=window.PARSELY.onload;window.PARSELY.onload=function(){e&&e(),n()}}if("function"!=typeof window.PARSELY.onReady)window.PARSELY.onReady=o;else{var t=window.PARSELY.onReady;window.PARSELY.onReady=function(){t&&t(),o()}}}else window.PARSELY={onload:n,onReady:o};!0===window.wpParselyDisableAutotrack&&(window.PARSELY.autotrack=!1)}(),function(){window.wp.i18n;var n,o,e;!function(n){n.Minutes10="10m",n.Hour="1h",n.Hours2="2h",n.Hours4="4h",n.Hours24="24h",n.Days7="7d",n.Days30="30d"}(n||(n={})),function(n){n.Views="views",n.AvgEngaged="avg_engaged"}(o||(o={})),function(n){n.Author="author",n.Section="section",n.Tag="tag",n.Unavailable="unavailable"}(e||(e={}));var t;void 0!==window.wpParselySiteId&&(null===(t=window.wpParselyHooks)||void 0===t||t.addAction("wpParselyOnLoad","wpParsely",(function(){var n,o,e,t,i,a;return e=this,t=void 0,a=function(){var e,t;return function(n,o){var e,t,i,a,r={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return a={next:l(0),throw:l(1),return:l(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function l(l){return function(u){return function(l){if(e)throw new TypeError("Generator is already executing.");for(;a&&(a=0,l[0]&&(r=0)),r;)try{if(e=1,t&&(i=2&l[0]?t.return:l[0]?t.throw||((i=t.return)&&i.call(t),0):t.next)&&!(i=i.call(t,l[1])).done)return i;switch(t=0,i&&(l=[2&l[0],i.value]),l[0]){case 0:case 1:i=l;break;case 4:return r.label++,{value:l[1],done:!1};case 5:r.label++,t=l[1],l=[0];continue;case 7:l=r.ops.pop(),r.trys.pop();continue;default:if(!((i=(i=r.trys).length>0&&i[i.length-1])||6!==l[0]&&2!==l[0])){r=0;continue}if(3===l[0]&&(!i||l[1]>i[0]&&l[1]0&&i[i.length-1])||6!==l[0]&&2!==l[0])){r=0;continue}if(3===l[0]&&(!i||l[1]>i[0]&&l[1]0&&i[i.length-1])||6!==s[0]&&2!==s[0])){o=0;continue}if(3===s[0]&&(!i||s[1]>i[0]&&s[1]=1e4&&(clearInterval(a),r("Telemetry library not loaded"))}),100);else r("Telemetry not enabled")}))},e.prototype.trackEvent=function(t,r){var n;this.isLoaded?(0!==t.indexOf(e.TRACKS_PREFIX)&&(t=e.TRACKS_PREFIX+t),this.isEventNameValid(t)?(r=this.prepareProperties(r),null===(n=this._tkq)||void 0===n||n.push(["recordEvent",t,r])):console.error("Error tracking event: Invalid event name")):console.error("Error tracking event: Telemetry not loaded")},e.prototype.isTelemetryEnabled=function(){return this.isEnabled},e.prototype.isProprietyValid=function(t){return e.PROPERTY_REGEX.test(t)},e.prototype.isEventNameValid=function(t){return e.EVENT_NAME_REGEX.test(t)},e.prototype.prepareProperties=function(e){return(e=this.sanitizeProperties(e)).parsely_version=wpParselyTracksTelemetry.version,wpParselyTracksTelemetry.user&&(e._ut=wpParselyTracksTelemetry.user.type,e._ui=wpParselyTracksTelemetry.user.id),wpParselyTracksTelemetry.vipgo_env&&(e.vipgo_env=wpParselyTracksTelemetry.vipgo_env),this.sanitizeProperties(e)},e.prototype.sanitizeProperties=function(e){var t=this,r={};return Object.keys(e).forEach((function(n){t.isProprietyValid(n)&&(r[n]=e[n])})),r},e.TRACKS_PREFIX="wpparsely_",e.EVENT_NAME_REGEX=/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/,e.PROPERTY_REGEX=/^[a-z_][a-z0-9_]*$/,e}(),n=(r.trackEvent,window.wp.data);if(r.getInstance().isTelemetryEnabled()){var i=[function(){return(0,e.useEffect)((function(){var e=function(){return(0,n.select)("core/block-editor").getBlocks()},t=new Set(e().map((function(e){return e.clientId}))),r=(0,n.subscribe)((function(){var r=e(),n=new Set(r.map((function(e){return e.clientId})));if(n.size!==t.size)for(var i=n.size>t.size,a=0,o=i?r:Array.from(t);a=t||n<0||b&&e-v>=s}function E(){var e=o();if(m(e))return g(e);p=setTimeout(E,function(e){var n=t-(e-d);return b?c(n,s-(e-v)):n}(e))}function g(e){return p=void 0,h&&u?w(e):(u=l=void 0,f)}function T(){var e=o(),n=m(e);if(u=arguments,l=this,d=e,n){if(void 0===p)return function(e){return v=e,p=setTimeout(E,t),y?w(e):f}(d);if(b)return clearTimeout(p),p=setTimeout(E,t),w(d)}return void 0===p&&(p=setTimeout(E,t)),f}return t=i(t)||0,r(n)&&(y=!!n.leading,s=(b="maxWait"in n)?a(i(n.maxWait)||0,t):s,h="trailing"in n?!!n.trailing:h),T.cancel=function(){void 0!==p&&clearTimeout(p),v=0,u=d=l=p=void 0},T.flush=function(){return void 0===p?f:g(o())},T}},218:function(e){e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},5:function(e){e.exports=function(e){return null!=e&&"object"==typeof e}},448:function(e,t,n){var r=n(239),o=n(5);e.exports=function(e){return"symbol"==typeof e||o(e)&&"[object Symbol]"==r(e)}},771:function(e,t,n){var r=n(639);e.exports=function(){return r.Date.now()}},841:function(e,t,n){var r=n(561),o=n(218),i=n(448),a=/^[-+]0x[0-9a-f]+$/i,c=/^0b[01]+$/i,u=/^0o[0-7]+$/i,l=parseInt;e.exports=function(e){if("number"==typeof e)return e;if(i(e))return NaN;if(o(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=o(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=r(e);var n=c.test(e);return n||u.test(e)?l(e.slice(2),n?2:8):a.test(e)?NaN:+e}}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var i=t[r]={exports:{}};return e[r](i,i.exports,n),i.exports}n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){"use strict";var e=window.wp.element,t=window.wp.plugins,r=function(){function e(){this._tkq=[],this.isLoaded=!1,this.isEnabled=!1,"undefined"!=typeof wpParselyTracksTelemetry&&(this.isEnabled=!0,this.loadTrackingLibrary())}return e.getInstance=function(){return window.wpParselyTelemetryInstance||Object.defineProperty(window,"wpParselyTelemetryInstance",{value:new e,writable:!1,configurable:!1,enumerable:!1}),window.wpParselyTelemetryInstance},e.prototype.loadTrackingLibrary=function(){var e=this,t=document.createElement("script");t.async=!0,t.src="//stats.wp.com/w.js",t.onload=function(){e.isLoaded=!0,e._tkq=window._tkq||[]},document.head.appendChild(t)},e.trackEvent=function(t){return n=this,r=arguments,i=function(t,n){var r;return void 0===n&&(n={}),function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(c){return function(u){return function(c){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,c[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&c[0]?r.return:c[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,c[1])).done)return o;switch(r=0,o&&(c=[2&c[0],o.value]),c[0]){case 0:case 1:o=c;break;case 4:return a.label++,{value:c[1],done:!1};case 5:a.label++,r=c[1],c=[0];continue;case 7:c=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==c[0]&&2!==c[0])){a=0;continue}if(3===c[0]&&(!o||c[1]>o[0]&&c[1]=1e4&&(clearInterval(i),n("Telemetry library not loaded"))}),100);else n("Telemetry not enabled")}))},e.prototype.trackEvent=function(t,n){var r;this.isLoaded?(0!==t.indexOf(e.TRACKS_PREFIX)&&(t=e.TRACKS_PREFIX+t),this.isEventNameValid(t)?(n=this.prepareProperties(n),null===(r=this._tkq)||void 0===r||r.push(["recordEvent",t,n])):console.error("Error tracking event: Invalid event name")):console.error("Error tracking event: Telemetry not loaded")},e.prototype.isTelemetryEnabled=function(){return this.isEnabled},e.prototype.isProprietyValid=function(t){return e.PROPERTY_REGEX.test(t)},e.prototype.isEventNameValid=function(t){return e.EVENT_NAME_REGEX.test(t)},e.prototype.prepareProperties=function(e){return(e=this.sanitizeProperties(e)).parsely_version=wpParselyTracksTelemetry.version,wpParselyTracksTelemetry.user&&(e._ut=wpParselyTracksTelemetry.user.type,e._ui=wpParselyTracksTelemetry.user.id),wpParselyTracksTelemetry.vipgo_env&&(e.vipgo_env=wpParselyTracksTelemetry.vipgo_env),this.sanitizeProperties(e)},e.prototype.sanitizeProperties=function(e){var t=this,n={};return Object.keys(e).forEach((function(r){t.isProprietyValid(r)&&(n[r]=e[r])})),n},e.TRACKS_PREFIX="wpparsely_",e.EVENT_NAME_REGEX=/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/,e.PROPERTY_REGEX=/^[a-z_][a-z0-9_]*$/,e}(),o=(r.trackEvent,window.wp.data),i=n(279),a=n.n(i);if(r.getInstance().isTelemetryEnabled()){var c=[function(){var t="wp-parsely/";return(0,e.useEffect)((function(){var e,n,i,c,u;return(n=void 0,i=void 0,c=void 0,u=function(){return function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(c){return function(u){return function(c){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,c[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&c[0]?r.return:c[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,c[1])).done)return o;switch(r=0,o&&(c=[2&c[0],o.value]),c[0]){case 0:case 1:o=c;break;case 4:return a.label++,{value:c[1],done:!1};case 5:a.label++,r=c[1],c=[0];continue;case 7:c=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==c[0]&&2!==c[0])){a=0;continue}if(3===c[0]&&(!o||c[1]>o[0]&&c[1]0)&&(t(),e())}))}))]}))},new(c||(c=Promise))((function(e,t){function r(e){try{a(u.next(e))}catch(e){t(e)}}function o(e){try{a(u.throw(e))}catch(e){t(e)}}function a(t){var n;t.done?e(t.value):(n=t.value,n instanceof c?n:new c((function(e){e(n)}))).then(r,o)}a((u=u.apply(n,i||[])).next())}))).then((function(){var n=(0,o.select)("core/block-editor").getBlocks(),i=a()((function(){var e=(0,o.select)("core/block-editor").getBlocks(),i=e.map((function(e){return e.clientId})),a=n.map((function(e){return e.clientId}));e.filter((function(e){return!a.includes(e.clientId)})).forEach((function(e){e.name.startsWith(t)&&r.trackEvent("block_added",{block:e.name})})),a.filter((function(e){return!i.includes(e)})).forEach((function(e){var o=n.find((function(t){return t.clientId===e}));o&&o.name.startsWith(t)&&r.trackEvent("block_removed",{block:o.name})})),n=e}),1e3);return e=(0,o.subscribe)(i,"core/block-editor")})),function(){e&&e()}}),[]),null}],u=e.createElement.apply(void 0,function(e,t,n){if(n||2===arguments.length)for(var r,o=0,i=t.length;o=16", @@ -2303,22 +2305,28 @@ } }, "node_modules/@floating-ui/dom": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.1.tgz", - "integrity": "sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "dev": true, "dependencies": { - "@floating-ui/core": "^1.4.1", - "@floating-ui/utils": "^0.1.1" + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" } }, + "node_modules/@floating-ui/dom/node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==", + "dev": true + }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", - "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "dev": true, "dependencies": { - "@floating-ui/dom": "^1.5.1" + "@floating-ui/dom": "^1.6.1" }, "peerDependencies": { "react": ">=16.8.0", @@ -3891,13 +3899,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", - "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz", + "integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==", "dev": true, "peer": true, "dependencies": { - "playwright": "1.40.1" + "playwright": "1.42.1" }, "bin": { "playwright": "cli.js" @@ -3971,16 +3979,6 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@preact/signals": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.2.2.tgz", @@ -3998,9 +3996,9 @@ } }, "node_modules/@preact/signals-core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.5.0.tgz", - "integrity": "sha512-U2diO1Z4i1n2IoFgMYmRdHWGObNrcuTRxyNEn7deSq2cru0vj0583HYQZHsAqcs7FE+hQyX3mjIV7LAfHCvy8w==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.5.1.tgz", + "integrity": "sha512-dE6f+WCX5ZUDwXzUIWNMhhglmuLpqJhuy3X3xHrhZYI0Hm2LyQwOu0l9mdPiWrVNsE+Q7txOnJPgtIqHCYoBVA==", "dev": true, "funding": { "type": "opencollective", @@ -5052,9 +5050,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.2.0.tgz", - "integrity": "sha512-+BVQlJ9cmEn5RDMUS8c2+TU6giLvzaHZ8sU/x0Jj7fk+6/46wPdwlgOPcpxS17CjcanBi/3VmGMqVr2rmbUmNw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.2.tgz", + "integrity": "sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.3.2", @@ -5073,6 +5071,7 @@ }, "peerDependencies": { "@jest/globals": ">= 28", + "@types/bun": "latest", "@types/jest": ">= 28", "jest": ">= 28", "vitest": ">= 0.32" @@ -5081,6 +5080,9 @@ "@jest/globals": { "optional": true }, + "@types/bun": { + "optional": true + }, "@types/jest": { "optional": true }, @@ -5166,9 +5168,9 @@ } }, "node_modules/@testing-library/react": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz", - "integrity": "sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.2.1.tgz", + "integrity": "sha512-sGdjws32ai5TLerhvzThYFbpnF9XtL65Cjf+gB0Dhr29BGqK+mAeN7SURSdu+eqgET4ANcWoC7FQpkaiGvBr+A==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -5431,9 +5433,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.11", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.11.tgz", - "integrity": "sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==", + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -5520,6 +5522,21 @@ "@types/node": "*" } }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "dev": true + }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -5776,9 +5793,9 @@ } }, "node_modules/@types/wordpress__block-editor": { - "version": "11.5.9", - "resolved": "https://registry.npmjs.org/@types/wordpress__block-editor/-/wordpress__block-editor-11.5.9.tgz", - "integrity": "sha512-yIFKaL40j/dDIDOH5hlvlTwwuTqb1SDgVYZQ8v498fAq+tYRQKZCJICpAunSmlMQpxHX1qs1+ZqmljyWvU1p4w==", + "version": "11.5.11", + "resolved": "https://registry.npmjs.org/@types/wordpress__block-editor/-/wordpress__block-editor-11.5.11.tgz", + "integrity": "sha512-NyEXf5QIbNTFd4rKRwuQ07PPqvok8ntb81+IHLXDzAxX1uWxOng98uwDhoordeNB1md8ab2iIo08KR2VXq2FNg==", "dev": true, "dependencies": { "@types/react": "*", @@ -5819,9 +5836,9 @@ } }, "node_modules/@types/wordpress__edit-post": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@types/wordpress__edit-post/-/wordpress__edit-post-7.5.5.tgz", - "integrity": "sha512-+T3eHrlJ1cWNoSqcTNLN+AfcAEAf/CLIGAiHvcM0wDSJ/nAmZbZKbWAynh2/zZgd90DqKeKeOXKY2JK9cbtoAA==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/wordpress__edit-post/-/wordpress__edit-post-7.5.6.tgz", + "integrity": "sha512-76G8fCTQb+NduBXTr8QjZpEs/w/Nbfy41G4xC2YpMsqw6vV0+g1KlQnfXWc3y0JyeCM9nz0/6dG4xl34hOFTTA==", "dev": true, "dependencies": { "@types/react": "*", @@ -6285,16 +6302,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.0.tgz", - "integrity": "sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/type-utils": "6.19.0", - "@typescript-eslint/utils": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -6320,13 +6337,13 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz", - "integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -6337,9 +6354,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", - "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -6350,12 +6367,12 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", - "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -6445,13 +6462,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.0.tgz", - "integrity": "sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.19.0", - "@typescript-eslint/utils": "6.19.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -6472,9 +6489,9 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", - "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -6485,13 +6502,13 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz", - "integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -6513,12 +6530,12 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", - "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -6566,9 +6583,9 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -6660,17 +6677,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.0.tgz", - "integrity": "sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/typescript-estree": "6.19.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -6685,13 +6702,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz", - "integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -6702,9 +6719,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", - "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -6715,13 +6732,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz", - "integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -6743,12 +6760,12 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", - "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -6796,9 +6813,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -7042,37 +7059,37 @@ } }, "node_modules/@wordpress/a11y": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.50.0.tgz", - "integrity": "sha512-eQiPGnxqiL1LgnHztFG0RGSFZ5phwR8B8Fr4lbJsFalsc9R/tOcjewvf2KN0yi2UlRA5ssAeiTP+tYmeAqtOHQ==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.53.0.tgz", + "integrity": "sha512-8Fg3c21oO0J6MjFb3UJ2pmDvwXLK9WVn2RdohqG36o3ft/oGD0FTtUw5ct5sgaCUcydFrCN7iNLJ/MLgbSrFiw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/dom-ready": "^3.50.0", - "@wordpress/i18n": "^4.50.0" + "@wordpress/dom-ready": "^3.53.0", + "@wordpress/i18n": "^4.53.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/api-fetch": { - "version": "6.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.47.0.tgz", - "integrity": "sha512-NA/jWDXoVtJmiVBYhlxts2UrgKJpJM+zTGzLCfRQCZUzpJYm3LonB8x+uCQ78nEyxCY397Esod3jnbquYjOr0Q==", + "version": "6.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.50.0.tgz", + "integrity": "sha512-a459l9WD58W5UyQkD6c54+4hv2hZzstDSzoJRMOZGSeEfbgN+49vgHLNEVhDHjNsS7Z6X2KeyyR/YoRgtXfloA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.50.0", - "@wordpress/url": "^3.51.0" + "@wordpress/i18n": "^4.53.0", + "@wordpress/url": "^3.54.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/autop": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-3.49.0.tgz", - "integrity": "sha512-bc0jUu8yOCioNFFgrO++XhdGU6QpL9HF9LeWxzayqp5Br4z9z7Zslp+KH1Gy6H2RNowEr8Fq4hZ7JwQ009EDmw==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-3.53.0.tgz", + "integrity": "sha512-t330lnDM8gb8G4U8Ky1qWvDxDsNn4FP+QVTrN72AAhjsz95VTQRsNY5xesedEN82e6FRdPIoeHyd/RuVqd6QTg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -7082,9 +7099,9 @@ } }, "node_modules/@wordpress/babel-plugin-import-jsx-pragma": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.33.0.tgz", - "integrity": "sha512-CjzruFKWgzU/mO/nnQJ2l9UlzZQpqS60UC6l2vNdJ9oD2nKHR5Oou6kNic3QhWDVJrBf2JUiJJ0TC280bykXmA==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.36.0.tgz", + "integrity": "sha512-xgBy9HnA0xL5e0Ipku7Ga3QimrfwTQ3njnN79mT8wNcim2APIlyiWSG3GndTdPoSGdrxGPv2ZrpqBdKsiGzoWQ==", "dev": true, "engines": { "node": ">=14" @@ -7094,9 +7111,9 @@ } }, "node_modules/@wordpress/babel-preset-default": { - "version": "7.34.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.34.0.tgz", - "integrity": "sha512-yjFOllyTktFHtcIEgU3ghXBn8lItzr5mPLf0xdSpe0cHceFYL1hT1oprhgRL+olZweaO96Yfm0qUCCKQfJBWsA==", + "version": "7.37.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.37.0.tgz", + "integrity": "sha512-XE9NUIoc428MHP3p6DMNjRV4Df97K9JHkzXwOwJjjHp00ce2ckh4wSkZh287Zi1X+uNcrROERtSp4jjWHUhvHA==", "dev": true, "dependencies": { "@babel/core": "^7.16.0", @@ -7105,9 +7122,9 @@ "@babel/preset-env": "^7.16.0", "@babel/preset-typescript": "^7.16.0", "@babel/runtime": "^7.16.0", - "@wordpress/babel-plugin-import-jsx-pragma": "^4.33.0", - "@wordpress/browserslist-config": "^5.33.0", - "@wordpress/warning": "^2.50.0", + "@wordpress/babel-plugin-import-jsx-pragma": "^4.36.0", + "@wordpress/browserslist-config": "^5.36.0", + "@wordpress/warning": "^2.53.0", "browserslist": "^4.21.10", "core-js": "^3.31.0", "react": "^18.2.0" @@ -7117,15 +7134,15 @@ } }, "node_modules/@wordpress/base-styles": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.39.0.tgz", - "integrity": "sha512-Obc6fKAnqzuWQSLgoce2yxhwMLd0nu4j7k3pVkBSzuitPw1LokmzHcHWPpgpMGR1T8CzKuj0Wsybcr2n3Xtyug==", + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.44.0.tgz", + "integrity": "sha512-Kgn5WsCmY1GPUhMQaUGSL8MqVUrstjYYel8PjAEo5VmKPICOaMBrip5dwy7zTomX4fj+sdV1NLIJJ6Bqi5zxnw==", "dev": true }, "node_modules/@wordpress/blob": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-3.49.0.tgz", - "integrity": "sha512-HYPMuXJx35uYlQC6JF9XXvPsOht2X8qJfXzGtxWb51OIC6DSRqh3f6s12fgPaNh9uElcSjQ4+Su286upu7S4vw==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-3.53.0.tgz", + "integrity": "sha512-fB1oXibUBfL2eTt303nbkbIJPP+SkKDGxEbYNIBrwaAoqp3oma7Q5uhguI8XFwKcdFw5I73U9bhlnnLMhK4FuA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -7135,50 +7152,49 @@ } }, "node_modules/@wordpress/block-editor": { - "version": "12.17.0", - "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-12.17.0.tgz", - "integrity": "sha512-np1ICMmScrSuDOQRYQqlDY35kOoQEHuckSCjJPQpjprutXaqG+Jk+RAeeHVgQ8Ze5B+QgkFLjNvYwRh11kYdqg==", + "version": "12.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-12.21.0.tgz", + "integrity": "sha512-B6c8YNWyv/zZPoEIo+Ks1W/RQQ9InUf99uZqlZSSevjDaKXZgFWWtecto60b8JQIqmpQJ32Y7LjdHcTMgAcFVQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "^3.49.0", - "@wordpress/api-fetch": "^6.46.0", - "@wordpress/blob": "^3.49.0", - "@wordpress/blocks": "^12.26.0", - "@wordpress/commands": "^0.20.0", - "@wordpress/components": "^25.15.0", - "@wordpress/compose": "^6.26.0", - "@wordpress/data": "^9.19.0", - "@wordpress/date": "^4.49.0", - "@wordpress/deprecated": "^3.49.0", - "@wordpress/dom": "^3.49.0", - "@wordpress/element": "^5.26.0", - "@wordpress/escape-html": "^2.49.0", - "@wordpress/hooks": "^3.49.0", - "@wordpress/html-entities": "^3.49.0", - "@wordpress/i18n": "^4.49.0", - "@wordpress/icons": "^9.40.0", - "@wordpress/is-shallow-equal": "^4.49.0", - "@wordpress/keyboard-shortcuts": "^4.26.0", - "@wordpress/keycodes": "^3.49.0", - "@wordpress/notices": "^4.17.0", - "@wordpress/preferences": "^3.26.0", - "@wordpress/private-apis": "^0.31.0", - "@wordpress/rich-text": "^6.26.0", - "@wordpress/style-engine": "^1.32.0", - "@wordpress/token-list": "^2.49.0", - "@wordpress/url": "^3.50.0", - "@wordpress/warning": "^2.49.0", - "@wordpress/wordcount": "^3.49.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/blob": "^3.53.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/commands": "^0.24.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/date": "^4.53.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/escape-html": "^2.53.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/keyboard-shortcuts": "^4.30.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/preferences": "^3.30.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/style-engine": "^1.36.0", + "@wordpress/token-list": "^2.53.0", + "@wordpress/url": "^3.54.0", + "@wordpress/warning": "^2.53.0", + "@wordpress/wordcount": "^3.53.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", "deepmerge": "^4.3.0", "diff": "^4.0.2", - "dom-scroll-into-view": "^1.2.1", "fast-deep-equal": "^3.1.3", "memize": "^2.1.0", "postcss": "^8.4.21", @@ -7197,81 +7213,45 @@ "react-dom": "^18.0.0" } }, - "node_modules/@wordpress/block-editor/node_modules/@wordpress/commands": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.20.0.tgz", - "integrity": "sha512-aQQCr3ViLwPEo/SEeW7FowA4zCfvypkO7eqTuTlcd+1E3ndRzlWA91rneo+l9GBUQ/elZzhc5Z0i2cMxHTMDRQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.15.0", - "@wordpress/data": "^9.19.0", - "@wordpress/element": "^5.26.0", - "@wordpress/i18n": "^4.49.0", - "@wordpress/icons": "^9.40.0", - "@wordpress/keyboard-shortcuts": "^4.26.0", - "@wordpress/private-apis": "^0.31.0", - "classnames": "^2.3.1", - "cmdk": "^0.2.0", - "rememo": "^4.0.2" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@wordpress/block-editor/node_modules/@wordpress/private-apis": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.31.0.tgz", - "integrity": "sha512-Hx2LJfkgbeAixXHDvi/rBly4+mShhrJfYXwyh6uTLnXkjp6OcPuBbCXhIfARw45lNdiqWdHoqXcAl1RTBFFd4g==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wordpress/block-library": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-8.24.0.tgz", - "integrity": "sha512-dztSfFa6S6QjlwS477HhPtjLHUc9UpC/qr2UFRvLA7euYdiEEIlYIHH/JlD9VHXJfQb5eA9lr9f3jMulU3PeSw==", + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-8.30.0.tgz", + "integrity": "sha512-7AC9/hvjhE6tGq2nL61oiCs5x/dt6Lcaj9ZL7/5Au5vkIlqOETt+9bECQmJEAYHh+PNo/jO7nX5CaEA5bRrP2Q==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.47.0", - "@wordpress/api-fetch": "^6.44.0", - "@wordpress/autop": "^3.47.0", - "@wordpress/blob": "^3.47.0", - "@wordpress/block-editor": "^12.15.0", - "@wordpress/blocks": "^12.24.0", - "@wordpress/components": "^25.13.0", - "@wordpress/compose": "^6.24.0", - "@wordpress/core-data": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/date": "^4.47.0", - "@wordpress/deprecated": "^3.47.0", - "@wordpress/dom": "^3.47.0", - "@wordpress/element": "^5.24.0", - "@wordpress/escape-html": "^2.47.0", - "@wordpress/hooks": "^3.47.0", - "@wordpress/html-entities": "^3.47.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/interactivity": "^3.0.0", - "@wordpress/keycodes": "^3.47.0", - "@wordpress/notices": "^4.15.0", - "@wordpress/primitives": "^3.45.0", - "@wordpress/private-apis": "^0.29.0", - "@wordpress/reusable-blocks": "^4.24.0", - "@wordpress/rich-text": "^6.24.0", - "@wordpress/server-side-render": "^4.24.0", - "@wordpress/url": "^3.48.0", - "@wordpress/viewport": "^5.24.0", - "@wordpress/wordcount": "^3.47.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/autop": "^3.53.0", + "@wordpress/blob": "^3.53.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/date": "^4.53.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/escape-html": "^2.53.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/interactivity": "^5.2.0", + "@wordpress/interactivity-router": "^1.3.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/patterns": "^1.14.0", + "@wordpress/primitives": "^3.51.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/reusable-blocks": "^4.30.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/server-side-render": "^4.30.0", + "@wordpress/url": "^3.54.0", + "@wordpress/viewport": "^5.30.0", + "@wordpress/wordcount": "^3.53.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", @@ -7304,9 +7284,9 @@ } }, "node_modules/@wordpress/block-serialization-default-parser": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-4.49.0.tgz", - "integrity": "sha512-9pQ6yxOhiFv+47iZWF3Te6N+PK+IFlEWgG3IpSIj3mWV6OI7FoM/+C2ePeR06OxE2cQHRkL9pAsECtK9eDJmCQ==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-4.53.0.tgz", + "integrity": "sha512-EfLBKT6igcuS8NnnFM3IAIefJJm5ooR5M8+ZnsMYQLgCnpQ8fikCs2r2UwBXxQ8DqONmMrXnORAld2o8C21dxw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -7316,27 +7296,27 @@ } }, "node_modules/@wordpress/blocks": { - "version": "12.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-12.26.0.tgz", - "integrity": "sha512-iIWUJmxGPXymf+X1rlHT0QxHV8+NzLfe96S3oKpX2UyFc/5H+eYWwyhA7u2S3kam/ss1DwAwdS7rRIMUHPU5PQ==", + "version": "12.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-12.30.0.tgz", + "integrity": "sha512-XBuT+I15TGA7B8AFE13W8CcXvfAIzu1w9V7NRKZZm8A7TCN4BTUSGUwufbd8Jw7qZ7yrh8+JwzHtBOL2GpBByw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/autop": "^3.49.0", - "@wordpress/blob": "^3.49.0", - "@wordpress/block-serialization-default-parser": "^4.49.0", - "@wordpress/compose": "^6.26.0", - "@wordpress/data": "^9.19.0", - "@wordpress/deprecated": "^3.49.0", - "@wordpress/dom": "^3.49.0", - "@wordpress/element": "^5.26.0", - "@wordpress/hooks": "^3.49.0", - "@wordpress/html-entities": "^3.49.0", - "@wordpress/i18n": "^4.49.0", - "@wordpress/is-shallow-equal": "^4.49.0", - "@wordpress/private-apis": "^0.31.0", - "@wordpress/rich-text": "^6.26.0", - "@wordpress/shortcode": "^3.49.0", + "@wordpress/autop": "^3.53.0", + "@wordpress/blob": "^3.53.0", + "@wordpress/block-serialization-default-parser": "^4.53.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/shortcode": "^3.53.0", "change-case": "^4.1.2", "colord": "^2.7.0", "fast-deep-equal": "^3.1.3", @@ -7357,18 +7337,6 @@ "react": "^18.0.0" } }, - "node_modules/@wordpress/blocks/node_modules/@wordpress/private-apis": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.31.0.tgz", - "integrity": "sha512-Hx2LJfkgbeAixXHDvi/rBly4+mShhrJfYXwyh6uTLnXkjp6OcPuBbCXhIfARw45lNdiqWdHoqXcAl1RTBFFd4g==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wordpress/blocks/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -7389,28 +7357,28 @@ } }, "node_modules/@wordpress/browserslist-config": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.33.0.tgz", - "integrity": "sha512-dv1ZlpqGk8gaSBJPP/Z/1uOuxjtP0EBsHVKInLRu6FWLTJkK8rnCeC3xJT3/2TtJ0rasLC79RoytfhXTOODVwg==", + "version": "5.36.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.36.0.tgz", + "integrity": "sha512-D4Y+MhZHAW4mDNFxHGacVpZgOmkkL9k5+TuVchC8cVSdpAt0VSkzKsXAumoQuEYUXyio/NMkhnU153FO+ci3cQ==", "dev": true, "engines": { "node": ">=14" } }, "node_modules/@wordpress/commands": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.18.0.tgz", - "integrity": "sha512-qJyAz2WtpRcJIKWtdkI5wWAnjx5aU9NdsZNW59xf9k9Uh3N1+1dvfFl3FJpR3pGCJv3dmuyFaWXJNYXqswXj/w==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.24.0.tgz", + "integrity": "sha512-siX+ouT9yvcdVYMdSY3REs3Tmnnzkv4L/dBhgJBrjJeMqh8badHR/4yqGEprPxuoRrU+Or5pwQDgq+HsvlxiaA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.13.0", - "@wordpress/data": "^9.17.0", - "@wordpress/element": "^5.24.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/keyboard-shortcuts": "^4.24.0", - "@wordpress/private-apis": "^0.29.0", + "@wordpress/components": "^27.1.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/keyboard-shortcuts": "^4.30.0", + "@wordpress/private-apis": "^0.35.0", "classnames": "^2.3.1", "cmdk": "^0.2.0", "rememo": "^4.0.2" @@ -7424,9 +7392,9 @@ } }, "node_modules/@wordpress/components": { - "version": "25.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-25.16.0.tgz", - "integrity": "sha512-voQuMsO5JbH+JW33TnWurwwvpSb8IQ4XU5wyVMubX4TUwadt+/2ToNJbZIDXoaJPei7vbM81Ft+pH+zGlN8CyA==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-27.1.0.tgz", + "integrity": "sha512-5y7xr4vmWqVzErM/BHKnOTcZpFkfPdhNJkRMXztL8NP6EcrkSPRexiUtaCTQqbNRKC/ZBtwR6P93JKKd8soKlQ==", "dev": true, "dependencies": { "@ariakit/react": "^0.3.12", @@ -7437,33 +7405,32 @@ "@emotion/serialize": "^1.0.2", "@emotion/styled": "^11.6.0", "@emotion/utils": "^1.0.0", - "@floating-ui/react-dom": "^2.0.1", + "@floating-ui/react-dom": "^2.0.8", "@types/gradient-parser": "0.1.3", "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.2.24", - "@wordpress/a11y": "^3.50.0", - "@wordpress/compose": "^6.27.0", - "@wordpress/date": "^4.50.0", - "@wordpress/deprecated": "^3.50.0", - "@wordpress/dom": "^3.50.0", - "@wordpress/element": "^5.27.0", - "@wordpress/escape-html": "^2.50.0", - "@wordpress/hooks": "^3.50.0", - "@wordpress/html-entities": "^3.50.0", - "@wordpress/i18n": "^4.50.0", - "@wordpress/icons": "^9.41.0", - "@wordpress/is-shallow-equal": "^4.50.0", - "@wordpress/keycodes": "^3.50.0", - "@wordpress/primitives": "^3.48.0", - "@wordpress/private-apis": "^0.32.0", - "@wordpress/rich-text": "^6.27.0", - "@wordpress/warning": "^2.50.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/date": "^4.53.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/escape-html": "^2.53.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/primitives": "^3.51.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/warning": "^2.53.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", "date-fns": "^2.28.0", "deepmerge": "^4.3.0", - "dom-scroll-into-view": "^1.2.1", "downshift": "^6.0.15", "fast-deep-equal": "^3.1.3", "framer-motion": "^10.13.0", @@ -7474,7 +7441,6 @@ "path-to-regexp": "^6.2.1", "re-resizable": "^6.4.0", "react-colorful": "^5.3.1", - "reakit": "^1.3.11", "remove-accents": "^0.5.0", "use-lilius": "^2.0.1", "uuid": "^9.0.1", @@ -7488,18 +7454,6 @@ "react-dom": "^18.0.0" } }, - "node_modules/@wordpress/components/node_modules/@wordpress/private-apis": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.32.0.tgz", - "integrity": "sha512-P7nxI/bGMDQhtlTfSe1Y2SDfrd20K5UMnTHbq+hmIkzBGRpNPbdGeNu2bZaZtIvmXk1OCR0Fkef+e6QqkOfYPg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wordpress/components/node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -7514,20 +7468,20 @@ } }, "node_modules/@wordpress/compose": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-6.27.0.tgz", - "integrity": "sha512-jbEQQ2znRyJTwUNR4m5BKaDyIsuK9TMZx0SKqP+FTfGqT3y7scOnQrHpK0kZdPji++/1cBbn3gSPBLCEmtmHRw==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-6.30.0.tgz", + "integrity": "sha512-YPyO0Ms3HUZSxb0ICzGe/55IWXAvzk0+iT4r3fiRtJaxrYtwz7XvLHkIJQ25bbue76lJbn4xFVD6hk30DiE7mA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "^3.50.0", - "@wordpress/dom": "^3.50.0", - "@wordpress/element": "^5.27.0", - "@wordpress/is-shallow-equal": "^4.50.0", - "@wordpress/keycodes": "^3.50.0", - "@wordpress/priority-queue": "^2.50.0", - "@wordpress/undo-manager": "^0.10.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/priority-queue": "^2.53.0", + "@wordpress/undo-manager": "^0.13.0", "change-case": "^4.1.2", "clipboard": "^2.0.11", "mousetrap": "^1.6.5", @@ -7540,36 +7494,24 @@ "react": "^18.0.0" } }, - "node_modules/@wordpress/compose/node_modules/@wordpress/undo-manager": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-0.10.0.tgz", - "integrity": "sha512-ODDqAL6BSvD+J7FV+sQTAaVHiPChh/4KBnKg8pb2ogg+Weq6VynthxDxGpQnN8FcMKB9ZoyS3SNIl8pVXLKIwA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0", - "@wordpress/is-shallow-equal": "^4.50.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wordpress/core-commands": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-0.16.0.tgz", - "integrity": "sha512-vo/hGAEzhiXpcVnd7sHPeDmod3JuNBPanRCUK5z440H6CEHGg0GuWP84pi34FXkvbpvoX9HWTF+5yl61GlXebw==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-0.22.0.tgz", + "integrity": "sha512-MclwrLXt9Lb9Fa4carfatguJZnlb1DqWjy2v7yYJtTB8QutcW1g/8pojkqdzNjcrr5y5vX2LgtAu7o8mSbX20A==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^12.15.0", - "@wordpress/commands": "^0.18.0", - "@wordpress/core-data": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/element": "^5.24.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/private-apis": "^0.29.0", - "@wordpress/router": "^0.16.0", - "@wordpress/url": "^3.48.0" + "@wordpress/block-editor": "^12.21.0", + "@wordpress/commands": "^0.24.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/router": "^0.22.0", + "@wordpress/url": "^3.54.0" }, "engines": { "node": ">=12" @@ -7580,27 +7522,27 @@ } }, "node_modules/@wordpress/core-data": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-6.26.0.tgz", - "integrity": "sha512-RI3uf3gHnjNyHgMm72IQlk0k83FJAYmLOGUJM01NuMvsVIxDxp03rfvy3lCfNy1+BknknOYFhUaX88NKrizgNA==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-6.30.0.tgz", + "integrity": "sha512-LVqRcyGwOHAgdtSNDiW5qkmjxCbatWeNyqnIjH58JKi8Y37T2hOPeAWaK7uL3NFr4eJ+kATKYfCC/v9VXWeKuQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.46.0", - "@wordpress/block-editor": "^12.17.0", - "@wordpress/blocks": "^12.26.0", - "@wordpress/compose": "^6.26.0", - "@wordpress/data": "^9.19.0", - "@wordpress/deprecated": "^3.49.0", - "@wordpress/element": "^5.26.0", - "@wordpress/html-entities": "^3.49.0", - "@wordpress/i18n": "^4.49.0", - "@wordpress/is-shallow-equal": "^4.49.0", - "@wordpress/private-apis": "^0.31.0", - "@wordpress/rich-text": "^6.26.0", - "@wordpress/sync": "^0.11.0", - "@wordpress/undo-manager": "^0.9.0", - "@wordpress/url": "^3.50.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/sync": "^0.15.0", + "@wordpress/undo-manager": "^0.13.0", + "@wordpress/url": "^3.54.0", "change-case": "^4.1.2", "equivalent-key-map": "^0.2.2", "fast-deep-equal": "^3.1.3", @@ -7616,18 +7558,6 @@ "react-dom": "^18.0.0" } }, - "node_modules/@wordpress/core-data/node_modules/@wordpress/private-apis": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.31.0.tgz", - "integrity": "sha512-Hx2LJfkgbeAixXHDvi/rBly4+mShhrJfYXwyh6uTLnXkjp6OcPuBbCXhIfARw45lNdiqWdHoqXcAl1RTBFFd4g==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wordpress/core-data/node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -7642,19 +7572,19 @@ } }, "node_modules/@wordpress/data": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.20.0.tgz", - "integrity": "sha512-3cm2te6NUj/X1zzmRO+WhueCanjocniX6sJFVzkg5mGXme6wFI8iSOnGPKlMkGcZGd0fVei1ydBKaIUMjrPBTQ==", + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.23.0.tgz", + "integrity": "sha512-CtVgMpP5CG2upzAQfB43cUDo8K24KIU/54FPZzZwM4PnOvpIZ5orhLQP3Gj/cmtr/U54d//DV52vCtuGD+qugg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/compose": "^6.27.0", - "@wordpress/deprecated": "^3.50.0", - "@wordpress/element": "^5.27.0", - "@wordpress/is-shallow-equal": "^4.50.0", - "@wordpress/priority-queue": "^2.50.0", - "@wordpress/private-apis": "^0.32.0", - "@wordpress/redux-routine": "^4.50.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/priority-queue": "^2.53.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/redux-routine": "^4.53.0", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", "is-plain-object": "^5.0.0", @@ -7670,26 +7600,14 @@ "react": "^18.0.0" } }, - "node_modules/@wordpress/data/node_modules/@wordpress/private-apis": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.32.0.tgz", - "integrity": "sha512-P7nxI/bGMDQhtlTfSe1Y2SDfrd20K5UMnTHbq+hmIkzBGRpNPbdGeNu2bZaZtIvmXk1OCR0Fkef+e6QqkOfYPg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wordpress/date": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-4.50.0.tgz", - "integrity": "sha512-FhfaG6YRXWmni66RjwhCB7rQNlLJ05+qTa/jXrj2UNWDNv/sfZ6Ky+b/rKrrUnLaIs9pGiW1195cSxsAS4EY3w==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-4.53.0.tgz", + "integrity": "sha512-Jfs7zYELB84Reo5DJ3pjr609IyWE3oJwubh5oZQI8E28hwj1OkGn6tY6qchg4QGnDyUcb+S9JDaJttG0wJGEfw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/deprecated": "^3.50.0", + "@wordpress/deprecated": "^3.53.0", "moment": "^2.29.4", "moment-timezone": "^0.5.40" }, @@ -7698,51 +7616,50 @@ } }, "node_modules/@wordpress/dependency-extraction-webpack-plugin": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-4.31.0.tgz", - "integrity": "sha512-Xpm8EEhi6e8GL1juYh/70AFbcE/ZVXJ3p47KMkkEsn5t+hG9QHjKe2lTj98v2r3rB+ampoK+whdV1w6gItXYpw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-6r1Nsq/yoJSqx35iqXeqjID8GGrN7mISZXWCjbuYLIRRY1FxU+wbenj2BsdUtyUuAv8tAUH79cpgO0poYEDMoQ==", "dev": true, "dependencies": { - "json2php": "^0.0.7", - "webpack-sources": "^3.2.2" + "json2php": "^0.0.7" }, "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { - "webpack": "^4.8.3 || ^5.0.0" + "webpack": "^5.0.0" } }, "node_modules/@wordpress/deprecated": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-3.50.0.tgz", - "integrity": "sha512-DL01l0Wlo3df9OcSGHP11Ot/nq0HytbdmD+iPkiCCRI6Xctepbs/DzRR2CO3qLrJkWn6RReFwZWZZjzI7lZUqg==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-3.53.0.tgz", + "integrity": "sha512-o6oXvOaEHYDbShDqmthVijiXo0cWdHpPbL6PFZQuqHWvDa9FyxLkfYym2/REZJIFaMJsf6BGZEJMrXTf/W+Nrg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.50.0" + "@wordpress/hooks": "^3.53.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/dom": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-3.50.0.tgz", - "integrity": "sha512-rMnV1ysGOHbKnmjLQYwGkT1co1iEkC3YsKrEObP8mklw1R7rbCy7fc2brIz7kqcHU1DRyg/+7wOCMkg8a/EV/Q==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-3.53.0.tgz", + "integrity": "sha512-mhRAqhDpgfo4cgtd9PGoVvQ7cJQbiMNOgL3BsvRWXDQO2De3cYM8gnJWoNbRW6UOdtMDSAKBM58iJ2PnmIPjbg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/deprecated": "^3.50.0" + "@wordpress/deprecated": "^3.53.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/dom-ready": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.50.0.tgz", - "integrity": "sha512-97tJpat1emXnwfGlJMiG6p37CpHJXDLmM/SIbsGJ0Oj8P4/TXbTuE9DNT1H8B1wKe5zD7kICjp48y91ugmgSrQ==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.53.0.tgz", + "integrity": "sha512-0sKnHwWoSxFrazehbxg4gZwTMCe1qIC4u2jUjVDviTlUMn3vsx5GdXNi0a7nYdR3Oiq7/a5JRKWsgXtIpeprnw==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -7751,15 +7668,15 @@ } }, "node_modules/@wordpress/e2e-test-utils": { - "version": "10.21.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-10.21.0.tgz", - "integrity": "sha512-Oh62GkqAKBIyD0IO3/Oa0l42yL/jbpTRDyh8H+t6gZbHWYTDvEGEr/LOqI9bk5Lwk7Jt5jpN6136FDwyMzHSXw==", + "version": "10.24.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-10.24.0.tgz", + "integrity": "sha512-y7//z4Fw85zvhD6nD3x8z2C0EhcQOaLQxRc2O0XBsM8nsOGE2NDJzTa7xwPtAOFP8StnytfgAK7xCSkM0UGYFg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.47.0", - "@wordpress/keycodes": "^3.50.0", - "@wordpress/url": "^3.51.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/url": "^3.54.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "node-fetch": "^2.6.0" @@ -7773,14 +7690,14 @@ } }, "node_modules/@wordpress/e2e-test-utils-playwright": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.16.0.tgz", - "integrity": "sha512-CktRj5/Cc/pAvTHXIAPIMrmmnb0VjtXbTGSjYG6pW/JI2YAmpwY2yBA+DlHJjqOIpcjDDj+sSsJomRSxT2chwQ==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.21.0.tgz", + "integrity": "sha512-/tnTaTcjJ4LzmZtsdZCTO77qmj3p2m4+Nmvf2v0iB3/AgRIKazndecfZ9YnY2guos5Kl8QBjVkdzQ9Xc28LOpw==", "dev": true, "dependencies": { - "@wordpress/api-fetch": "^6.45.0", - "@wordpress/keycodes": "^3.48.0", - "@wordpress/url": "^3.49.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/url": "^3.54.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "get-port": "^5.1.1", @@ -7808,42 +7725,42 @@ } }, "node_modules/@wordpress/edit-post": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-7.24.0.tgz", - "integrity": "sha512-lfbD+3KGjSoCwzG7WtfyoWkSlyGiE+RWNUzehpN5m/Y0mc/YNA5j4EkNNwE8Qr0Iz4GypoLw7lXJBPNCqOP7AQ==", + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-7.30.0.tgz", + "integrity": "sha512-vnQjrLaG830Ysx05CHkLcqtHs+Dtj8oQBezGS4Jhg9S5e14Y0HZCIeFJ3GARgSDZoCTeX87WSyEDf7XlpZKxJA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.47.0", - "@wordpress/api-fetch": "^6.44.0", - "@wordpress/block-editor": "^12.15.0", - "@wordpress/block-library": "^8.24.0", - "@wordpress/blocks": "^12.24.0", - "@wordpress/commands": "^0.18.0", - "@wordpress/components": "^25.13.0", - "@wordpress/compose": "^6.24.0", - "@wordpress/core-commands": "^0.16.0", - "@wordpress/core-data": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/deprecated": "^3.47.0", - "@wordpress/dom": "^3.47.0", - "@wordpress/editor": "^13.24.0", - "@wordpress/element": "^5.24.0", - "@wordpress/hooks": "^3.47.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/interface": "^5.24.0", - "@wordpress/keyboard-shortcuts": "^4.24.0", - "@wordpress/keycodes": "^3.47.0", - "@wordpress/media-utils": "^4.38.0", - "@wordpress/notices": "^4.15.0", - "@wordpress/plugins": "^6.15.0", - "@wordpress/preferences": "^3.24.0", - "@wordpress/private-apis": "^0.29.0", - "@wordpress/url": "^3.48.0", - "@wordpress/viewport": "^5.24.0", - "@wordpress/warning": "^2.47.0", - "@wordpress/widgets": "^3.24.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/block-library": "^8.30.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/commands": "^0.24.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-commands": "^0.22.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/editor": "^13.30.0", + "@wordpress/element": "^5.30.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/interface": "^5.30.0", + "@wordpress/keyboard-shortcuts": "^4.30.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/media-utils": "^4.44.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/plugins": "^6.21.0", + "@wordpress/preferences": "^3.30.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/url": "^3.54.0", + "@wordpress/viewport": "^5.30.0", + "@wordpress/warning": "^2.53.0", + "@wordpress/widgets": "^3.30.0", "classnames": "^2.3.1", "memize": "^2.1.0", "rememo": "^4.0.2" @@ -7857,42 +7774,42 @@ } }, "node_modules/@wordpress/editor": { - "version": "13.25.0", - "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-13.25.0.tgz", - "integrity": "sha512-1waT0ZG6ii98eOuqbOJMEE9vHp1Y8BGlnb37rwiFn61yeUnWuvH2qcHyPiRh4tUDYp3rHACa+P0joN4L32MeHA==", + "version": "13.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-13.30.0.tgz", + "integrity": "sha512-++tYN1rCLUCjVZm8/qknvmMZXTbM/M5EQcVy0qqlwdryMfR6tQcbze2lkJeN6dbWlrOwmJ+o+J/qwwxgUMiMQg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.48.0", - "@wordpress/api-fetch": "^6.45.0", - "@wordpress/blob": "^3.48.0", - "@wordpress/block-editor": "^12.16.0", - "@wordpress/blocks": "^12.25.0", - "@wordpress/commands": "^0.19.0", - "@wordpress/components": "^25.14.0", - "@wordpress/compose": "^6.25.0", - "@wordpress/core-data": "^6.25.0", - "@wordpress/data": "^9.18.0", - "@wordpress/date": "^4.48.0", - "@wordpress/deprecated": "^3.48.0", - "@wordpress/dom": "^3.48.0", - "@wordpress/element": "^5.25.0", - "@wordpress/hooks": "^3.48.0", - "@wordpress/html-entities": "^3.48.0", - "@wordpress/i18n": "^4.48.0", - "@wordpress/icons": "^9.39.0", - "@wordpress/keyboard-shortcuts": "^4.25.0", - "@wordpress/keycodes": "^3.48.0", - "@wordpress/media-utils": "^4.39.0", - "@wordpress/notices": "^4.16.0", - "@wordpress/patterns": "^1.9.0", - "@wordpress/preferences": "^3.25.0", - "@wordpress/private-apis": "^0.30.0", - "@wordpress/reusable-blocks": "^4.25.0", - "@wordpress/rich-text": "^6.25.0", - "@wordpress/server-side-render": "^4.25.0", - "@wordpress/url": "^3.49.0", - "@wordpress/wordcount": "^3.48.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/blob": "^3.53.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/commands": "^0.24.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/date": "^4.53.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/keyboard-shortcuts": "^4.30.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/media-utils": "^4.44.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/patterns": "^1.14.0", + "@wordpress/preferences": "^3.30.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/reusable-blocks": "^4.30.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/server-side-render": "^4.30.0", + "@wordpress/url": "^3.54.0", + "@wordpress/wordcount": "^3.53.0", "classnames": "^2.3.1", "date-fns": "^2.28.0", "memize": "^2.1.0", @@ -7908,54 +7825,16 @@ "react-dom": "^18.0.0" } }, - "node_modules/@wordpress/editor/node_modules/@wordpress/commands": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.19.0.tgz", - "integrity": "sha512-HGI59spV/efsbBaZ1/MkFFIjCSiQRd3u+oEAyRY8KWhxZBkEbJPnxBVb1I+LZ4GkhMNwRLAeo2VrI1HoYbs6eQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.14.0", - "@wordpress/data": "^9.18.0", - "@wordpress/element": "^5.25.0", - "@wordpress/i18n": "^4.48.0", - "@wordpress/icons": "^9.39.0", - "@wordpress/keyboard-shortcuts": "^4.25.0", - "@wordpress/private-apis": "^0.30.0", - "classnames": "^2.3.1", - "cmdk": "^0.2.0", - "rememo": "^4.0.2" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@wordpress/editor/node_modules/@wordpress/private-apis": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.30.0.tgz", - "integrity": "sha512-mkz2QtbSVNAsFNXBni5XMLV1KYhQAx1vyC5KcEyeQADiRkRUW6XJ+u53WwQfpdjvsEQhkyGpK13Rl7gt3KOpeQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wordpress/element": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.27.0.tgz", - "integrity": "sha512-IA5LTAfx5bDNXULPmctcNb/04i4JcnIReG0RAuPgrZ8lbMZWUxGFymh10PEQjs7ZJ++qGsI6E+6JISpjkRaDQQ==", + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.30.0.tgz", + "integrity": "sha512-KH+KdZ1jzLRgA65Ez6Uy5dVbkS2az0uk1lDUpPRhApEY2J12SbsD/aVuznP/huO2Af+hyh4DDqbVS817Abcy2g==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", - "@wordpress/escape-html": "^2.50.0", + "@wordpress/escape-html": "^2.53.0", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.2.0", @@ -7966,9 +7845,9 @@ } }, "node_modules/@wordpress/env": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-9.2.0.tgz", - "integrity": "sha512-2gl65WYbkuTjnW2SHKjeqdpLTgnPc/xVvFiwG+2p/RJwDHSuw1xXSdFqFUh3+wC/4cuXy9b2ZBm/SYsBoc8DDw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-9.5.0.tgz", + "integrity": "sha512-dxIosImyvkqUqPgCcOUCTgnLQRG3dZhnnNomEnsY0z5stbN+2IS6EJq3mZ/oJDitcJjrqB0dPopdUTVS/9bWMA==", "dev": true, "dependencies": { "chalk": "^4.0.0", @@ -8059,9 +7938,9 @@ } }, "node_modules/@wordpress/escape-html": { - "version": "2.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.50.0.tgz", - "integrity": "sha512-hBvoMCEZocziZDGCmBanSO+uupnd054mxd7FQ6toQ4UnsZ4JwXSmEC72W2Ed+cRGB1DeJDD0dY9iC0b4xkumsQ==", + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.53.0.tgz", + "integrity": "sha512-K2c6jg7qTGZIFj7uTCFR4FTK8PqHM4El7zdPAuK2apnZqhbdJEfH1/ogK+QZtn1VctyOXl0Mc+vzWoLUncey3g==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -8071,16 +7950,16 @@ } }, "node_modules/@wordpress/eslint-plugin": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-17.7.0.tgz", - "integrity": "sha512-JSFaCogE0WlZpl0SV4q8DK8G6jwDjEzXRzOsgesWilea4OuVp1KxCamkddTorRNM3QAbjrGuPJ4NYaGrNG9QsA==", + "version": "17.10.0", + "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-17.10.0.tgz", + "integrity": "sha512-fMmMzBMR8z7p2yYTMtEEnzoYmdFdv0HdrM2b7s9693fYxtYQv/FaxUKdep6slMiVt/DBoPUmuDGgZsttzOTwng==", "dev": true, "dependencies": { "@babel/eslint-parser": "^7.16.0", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "^7.34.0", - "@wordpress/prettier-config": "^3.7.0", + "@wordpress/babel-preset-default": "^7.37.0", + "@wordpress/prettier-config": "^3.10.0", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", @@ -8141,9 +8020,9 @@ } }, "node_modules/@wordpress/hooks": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.50.0.tgz", - "integrity": "sha512-YIhwT1y0ss7Byfz46NBx08EUmXzWMu+g5DCY7FMuDNhwxSEoZMB8edKMiwNmFk4mFKBCnXM1d5FeONUPIUkJwg==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.53.0.tgz", + "integrity": "sha512-Ul8iS+bzJMuN/ZD1HTcs3fwXC6eKLCvJJZmb61tEQ+Z3dubjf6vFQizE6Tl3ZaVlcc2/rtwb0rdEPLfnR6ePFg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -8153,9 +8032,9 @@ } }, "node_modules/@wordpress/html-entities": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-3.50.0.tgz", - "integrity": "sha512-DBRgShv6FLtDpapoTgmEx//6uHeq+mk5zKhAWAAqu6+/6LqOm/TCoUTxb0E2xtHh4oRBgU5nYC92pObRaczFdQ==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-3.53.0.tgz", + "integrity": "sha512-fqO6oJnBs1hLNtRLQLIYkKcnISpwf/ts4hOcBr+SN1pusTM3Om9fSUYjjRwXzjnqQsm/Tqjnh3GyDNzmmyuz5w==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -8165,13 +8044,13 @@ } }, "node_modules/@wordpress/i18n": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.50.0.tgz", - "integrity": "sha512-FkA2se6HMQm4eFC+/kTWvWQqs51VxpZuvY2MlWUp/L1r1d/dMBHXu049x86+/+6yk3ZNqiK5h6j6Z76dvPHZ4w==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.53.0.tgz", + "integrity": "sha512-Ovp00blCGhgKwtLQbxT/gweB9r9vGPLkneM28KhCQvgHIlpyDRESIr+CMIDov7KxKnO2gzfnwfrNhXgZX+a/3Q==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.50.0", + "@wordpress/hooks": "^3.53.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "sprintf-js": "^1.1.1", @@ -8185,51 +8064,64 @@ } }, "node_modules/@wordpress/icons": { - "version": "9.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.41.0.tgz", - "integrity": "sha512-L4fp9ZdxGBpMk3o2YqABgiPHNoHyu9Enid7JNkCdWP8iUgk7dEiDvo/XoiWPTAeNbF6W8Nqu54635mq01es0NQ==", + "version": "9.44.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.44.0.tgz", + "integrity": "sha512-8VxsFkcUYGCk6qfrriSOxOFSDSgl4tNRnJo7019ABj8mTr4pqdJC7tYz88rOvtBbx9tswWXLBJM49vaBG8mOpw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.27.0", - "@wordpress/primitives": "^3.48.0" + "@wordpress/element": "^5.30.0", + "@wordpress/primitives": "^3.51.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/interactivity": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-3.0.0.tgz", - "integrity": "sha512-/UqtStwrUWY5/HsXe7i/0p3c77+mEmqDOFxqK2VdMdl5FF2PeFqqVhAJrsBE40Sbtia2Im+9yH/rfyo4zNtBnw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-5.2.0.tgz", + "integrity": "sha512-0TmHT2LYpkONpP+7KwZrdnG0T1eoWsRG3DYmei1i05aWbThSTBxRsZCgDrURgLMPOvLvjumBtzXLHZoUSKvV+g==", + "dev": true, + "dependencies": { + "@preact/signals": "^1.2.2", + "deepsignal": "^1.4.0", + "preact": "^10.19.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@wordpress/interactivity-router": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity-router/-/interactivity-router-1.3.0.tgz", + "integrity": "sha512-NRrfBwGzTTeKMmQHIWp4yCAmBNGzHcSc2ot6WNbFmX3WtGkIvYHG6pKhSfl2Hx9ZpJN7og7II6J/VjMGSfelRQ==", "dev": true, "dependencies": { - "@preact/signals": "^1.1.3", - "deepsignal": "^1.3.6", - "preact": "^10.13.2" + "@wordpress/interactivity": "^5.2.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/interface": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-5.24.0.tgz", - "integrity": "sha512-zRzYG4QM5nPJ2FAuLRxyW7RW1F+Pz3qpPpSBUvQusjnbc8r9/gLFtXXIALHukOF6l6uQ67a1rm4752aFoCWj0A==", + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-5.30.0.tgz", + "integrity": "sha512-HIHQKI/tkIuJzM8unZeYsOPgaEPo4gZ81BmopKR5NRK4qNlhK42u9gpW4eUOwgxqm+Ngw5QFLiEX4vnOU1C8+A==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.47.0", - "@wordpress/components": "^25.13.0", - "@wordpress/compose": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/deprecated": "^3.47.0", - "@wordpress/element": "^5.24.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/plugins": "^6.15.0", - "@wordpress/preferences": "^3.24.0", - "@wordpress/viewport": "^5.24.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/plugins": "^6.21.0", + "@wordpress/preferences": "^3.30.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/viewport": "^5.30.0", "classnames": "^2.3.1" }, "engines": { @@ -8241,9 +8133,9 @@ } }, "node_modules/@wordpress/is-shallow-equal": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.50.0.tgz", - "integrity": "sha512-lX0fMa1f/TwWYYF+Oj0MG2Eze4Bb+vsnhXX6X1l+Ri3PG34wWGonjq729qHbJRDwm8o1y9GeswCgESIpuAm9wg==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.53.0.tgz", + "integrity": "sha512-/90Nd1u90yTQxtwv6c+TnGyBFvxrF04pfo1KhdodCHlX/+sBn4CASgSvQAW2+FXemNqfw0rC4mBG/Sa8xhtC5A==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -8253,9 +8145,9 @@ } }, "node_modules/@wordpress/jest-console": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.19.0.tgz", - "integrity": "sha512-x35izGNCLo7xoK770I7O/+m6sE/a9lmo6QqyDoR1AZaUwk0PAY35EGrbbi3FfXeReTXBRNJ1MpnQyvskg8o/Gw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.24.0.tgz", + "integrity": "sha512-mCcoEGrFDN78QMWSXVH3B5RVdwNNzsODA8g6LSbmxKY5T/4xuOgF/Kj6mS0YSrCxyOiDUTW9VgHoQtx2MwS+Qg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -8269,12 +8161,12 @@ } }, "node_modules/@wordpress/jest-preset-default": { - "version": "11.19.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.19.0.tgz", - "integrity": "sha512-9BbUDZaa6Cg9dz+JyfOe30C8JJrhCkyaFqCqSNJEcyB4KK83qp2QRkblVXABmHarw4oPfg+OJLLALIAA045a0w==", + "version": "11.24.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.24.0.tgz", + "integrity": "sha512-W3y5PYdOwzm1oKdx+8XZoLeXEV4FI/LnYJpcnKxIyQxfPm4cI8WlRMUT6DAyW6nKv67a0FUwoA7XJ9kXH1pASA==", "dev": true, "dependencies": { - "@wordpress/jest-console": "^7.19.0", + "@wordpress/jest-console": "^7.24.0", "babel-jest": "^29.6.2" }, "engines": { @@ -8286,15 +8178,15 @@ } }, "node_modules/@wordpress/keyboard-shortcuts": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-4.26.0.tgz", - "integrity": "sha512-ijCDTSKmWUP4sanucgrOqhSaxqBE1nbR2FzBEITSSfh2x1i0IK5rzF5BL3waV4mWKuSe0UmpPz5vnqKvijc+Ug==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-4.30.0.tgz", + "integrity": "sha512-ICEFcw6p/uuXMddnDqMglR74p/uAUX1Rr4RM1BoZ7BqmopPTey4hQz8bT/FfogBpjl0QulD7D6rh19aiZ/ppfQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/data": "^9.19.0", - "@wordpress/element": "^5.26.0", - "@wordpress/keycodes": "^3.49.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/keycodes": "^3.53.0", "rememo": "^4.0.2" }, "engines": { @@ -8305,43 +8197,43 @@ } }, "node_modules/@wordpress/keycodes": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.50.0.tgz", - "integrity": "sha512-ykWpyCbgwcaT8i5kSfotYtd2oOHyMDpWEYR73InYrzEhl7pnS3wD7hi/KfeKLvMfYhbysUXlCVr6q/oH+qK/DQ==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.53.0.tgz", + "integrity": "sha512-fNmjwZCdKg0EqXUQTJzdLj4sC58vIp8UqRKg+DeHQX4xAjLyTN8/JzMvmxPFF2nv57a1J5FLtOONqdrxaETGYg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.50.0" + "@wordpress/i18n": "^4.53.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/media-utils": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@wordpress/media-utils/-/media-utils-4.39.0.tgz", - "integrity": "sha512-vJRqqnEIsAvgy82daf+qE87ncB4o8x2Y/1NbvqUJUvU/B+xDxRxZeOYdwcYIhb7VNFkWo+DMxtIuhiNX67XiTQ==", + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@wordpress/media-utils/-/media-utils-4.44.0.tgz", + "integrity": "sha512-o6QqamGg3CwwFxwZW+lwmsihUr5csY+3wpeLrvC2xxJEGo6Vr/GVnFGtBmFRhRaa91aT4CoX/xlzTXpRRheoCg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.45.0", - "@wordpress/blob": "^3.48.0", - "@wordpress/element": "^5.25.0", - "@wordpress/i18n": "^4.48.0" + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/blob": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/notices": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-4.17.0.tgz", - "integrity": "sha512-EH7f4YDQUtuY+UlS8OIv0bjXXK+SGMGPQNlecSKFoP3QBoXZy5zhVDAfr4vewPE19t3gWaf22zPtF0NTl06a2g==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-4.21.0.tgz", + "integrity": "sha512-clyPRDhVbG7g1n1JDLLOimfBi5e6b9EekZv/P9amxCQxAvFquwDoAvQtUbuz6unF8sFLRtvLO5LnNqGwEnL/eg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.49.0", - "@wordpress/data": "^9.19.0" + "@wordpress/a11y": "^3.53.0", + "@wordpress/data": "^9.23.0" }, "engines": { "node": ">=12" @@ -8351,9 +8243,9 @@ } }, "node_modules/@wordpress/npm-package-json-lint-config": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.33.0.tgz", - "integrity": "sha512-GBVGcn6xAqrWQueSlMVMHoebGsHvildWwcJ/lIpxh7i7V/VBoc9Z8rdUEKAip6lTjZx+mCmzXQH4hU3QdNA/RA==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.38.0.tgz", + "integrity": "sha512-RT5dDPaHiPNzVTcLV6HTHArRBPzAhQPMKZi2BX16rrlYLwcr9TNMVJQ787RXxLmkr83BvudZqsHF4va06FwVLQ==", "dev": true, "engines": { "node": ">=14" @@ -8363,27 +8255,26 @@ } }, "node_modules/@wordpress/patterns": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-1.9.0.tgz", - "integrity": "sha512-acuP9k41lGQD/jMcAbiChx5XRP5HUfyre1hJeL+eqAC/SYF/BnDKrhxugoXnOIpJDxrlOZ968csZukbj088QnQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-1.14.0.tgz", + "integrity": "sha512-QRuakdYJCPpnY8sZQzad2Gyr+oWk6/VSMpTDbLsZ4b44XIBk6eDtYloUNaoRMzbjQDaee69GV24xXZnzzDdUuA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.48.0", - "@wordpress/block-editor": "^12.16.0", - "@wordpress/blocks": "^12.25.0", - "@wordpress/components": "^25.14.0", - "@wordpress/compose": "^6.25.0", - "@wordpress/core-data": "^6.25.0", - "@wordpress/data": "^9.18.0", - "@wordpress/element": "^5.25.0", - "@wordpress/html-entities": "^3.48.0", - "@wordpress/i18n": "^4.48.0", - "@wordpress/icons": "^9.39.0", - "@wordpress/notices": "^4.16.0", - "@wordpress/private-apis": "^0.30.0", - "@wordpress/url": "^3.49.0", - "nanoid": "^3.3.4" + "@wordpress/a11y": "^3.53.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/url": "^3.54.0" }, "engines": { "node": ">=16.0.0" @@ -8393,31 +8284,19 @@ "react-dom": "^18.0.0" } }, - "node_modules/@wordpress/patterns/node_modules/@wordpress/private-apis": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.30.0.tgz", - "integrity": "sha512-mkz2QtbSVNAsFNXBni5XMLV1KYhQAx1vyC5KcEyeQADiRkRUW6XJ+u53WwQfpdjvsEQhkyGpK13Rl7gt3KOpeQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wordpress/plugins": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-6.18.0.tgz", - "integrity": "sha512-m2BRJ5BApIMwT2Ck5E5yD8pS3RiIoOvWhzsYWrRqRfwjRhc6K46BreCbkiHgduBaFgzDIWpujlUHkYtdl27RoQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-6.21.0.tgz", + "integrity": "sha512-q3AVdhIk7/mTrPMY3llEQJvupbsN+1L6m2YF3AYnPB9VVRTRb4RDlor28YWxPwlliRSdTy2Q5t5orWtNDD0s4w==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.16.0", - "@wordpress/compose": "^6.27.0", - "@wordpress/element": "^5.27.0", - "@wordpress/hooks": "^3.50.0", - "@wordpress/icons": "^9.41.0", - "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/element": "^5.30.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/is-shallow-equal": "^4.53.0", "memize": "^2.0.1" }, "engines": { @@ -8429,12 +8308,12 @@ } }, "node_modules/@wordpress/postcss-plugins-preset": { - "version": "4.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.32.0.tgz", - "integrity": "sha512-+4+chYW8pRd7Irzm8lXom5Axs765q4me1mT+FBskfotUroAvoJtmfAybmyhIvTirTwLaN7ugOYiSBjAD6M7+rg==", + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.37.0.tgz", + "integrity": "sha512-9k0OFThyn73hmZ0NRWtrYDEHk8BHwzMLrovqtt9fsBQRQviz4kjLOFkGvSQmvFPbnaMK1ZG5WOhV8/RkKK8tig==", "dev": true, "dependencies": { - "@wordpress/base-styles": "^4.39.0", + "@wordpress/base-styles": "^4.44.0", "autoprefixer": "^10.2.5" }, "engines": { @@ -8445,18 +8324,21 @@ } }, "node_modules/@wordpress/preferences": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-3.26.0.tgz", - "integrity": "sha512-8fXN9T1sh9g6kl3ta0BWlZKeqlvMGj2VhNd564zZdfOsEojW1Fhq2RoLahcp2BnMmSojdgPCSQQ8O2IdirwDyA==", + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-3.30.0.tgz", + "integrity": "sha512-8GfcEWerwliMTs/hpKbYHxF0SnH/ghbpyUHk13hdZsJwIYFN/DGS9KPbeQmoMdJIOS5YUxhQ1dXCxJIjBmpSlA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.49.0", - "@wordpress/components": "^25.15.0", - "@wordpress/data": "^9.19.0", - "@wordpress/element": "^5.26.0", - "@wordpress/i18n": "^4.49.0", - "@wordpress/icons": "^9.40.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/private-apis": "^0.35.0", "classnames": "^2.3.1" }, "engines": { @@ -8468,9 +8350,9 @@ } }, "node_modules/@wordpress/prettier-config": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-3.7.0.tgz", - "integrity": "sha512-JRTc5p7UxtcPkqdSDXSFJoJnVuS510uiRVz8anXEl5nuOx5p+SJAzi9QPrxTgOE8bN3wRABH4eIhfOcta4CFdg==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-3.10.0.tgz", + "integrity": "sha512-0zA3K1zDyRjUhTY+zKfBvQMKqEbYK/hC3NOabEWZ++pvT5JYJrD7ZVXE+l5TDVd/d2rqxM0eLssh/yIyWyaeSQ==", "dev": true, "engines": { "node": ">=14" @@ -8480,13 +8362,13 @@ } }, "node_modules/@wordpress/primitives": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.48.0.tgz", - "integrity": "sha512-uBoMxpl+FiZF6aRXH/+Hwol4EAL6QqlNSaGF1IzEwklFzdRF1m5wTM4vh21w8Bq7lgxiuAqyueY7X5u32v+zPw==", + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.51.0.tgz", + "integrity": "sha512-UKz0h3BIU4hDMXnNlLZ6CZCe7eWuhDhzLj25+Ldfb71RuXMoqdH31ZN3gwIljVrLRWcBKVtgp9ULmuhdGvwzDA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.27.0", + "@wordpress/element": "^5.30.0", "classnames": "^2.3.1" }, "engines": { @@ -8494,9 +8376,9 @@ } }, "node_modules/@wordpress/priority-queue": { - "version": "2.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.50.0.tgz", - "integrity": "sha512-21E842EVFYUd1ZrNTLAW57IyloDCUZr6h1Te6BgqKoeKOEteoTQwA9BemMzZJUiThUSZymW94ot0Omb+C8VX2g==", + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.53.0.tgz", + "integrity": "sha512-tWsEOzstjE05DGyP9lGtwD6Ver6O1emKFQCHVXqS5kPfTg44Tm4/JTaCpnYYAf/+ki9ueZgTczqIdukCu+PDFA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -8507,9 +8389,9 @@ } }, "node_modules/@wordpress/private-apis": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.29.0.tgz", - "integrity": "sha512-8t4au9+IXXgJlxxOuYVYi8PKp0uWajNYILNfqCLB65jQEClzGNMQhU6MeJ9+kHN3gdOltMk7UzG28X+FTDlmkQ==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.35.0.tgz", + "integrity": "sha512-ta+k1VfwFFj3+JjpANwhancgEZEznYOvdVcKeLAlhKbM10IwIX2jGqwTjHsoN+C4o/8eoLi4RgJgdDWHGXiGrw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -8519,9 +8401,9 @@ } }, "node_modules/@wordpress/redux-routine": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-4.50.0.tgz", - "integrity": "sha512-giHjQYhmFDCpeNEnsZKP0JNPBnpuQwsoxLmHAUUSNFWAmd4rtnNnG6M8HuqOLmgYTvEa8Hlx3Bl+reTGvrtI2g==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-4.53.0.tgz", + "integrity": "sha512-95RZKRjCf++y7Lur2pGWP+e70buJ94+gyASTewlMfxeK3l6QWempghLEJNMrOSyx+FckFJnsD8qQyXVCDA7Cwg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -8537,23 +8419,23 @@ } }, "node_modules/@wordpress/reusable-blocks": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-4.25.0.tgz", - "integrity": "sha512-R7ysUJMRcxfGeF0Ly7/5QK7L32aHsGHXamSQY5h+aBhdNhKRtElWZKvzaKiw7AmKUqWpCQtpa8elFm2Iim//vw==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-4.30.0.tgz", + "integrity": "sha512-1tgjuoTgWqMSFFEXpP1od1TxMDdlEw4MTvqrl9ItZxERdEBggzRFhwhbyW2LpWe9C+g0svqVEUwv0nVjEUV9Zg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^12.16.0", - "@wordpress/blocks": "^12.25.0", - "@wordpress/components": "^25.14.0", - "@wordpress/core-data": "^6.25.0", - "@wordpress/data": "^9.18.0", - "@wordpress/element": "^5.25.0", - "@wordpress/i18n": "^4.48.0", - "@wordpress/icons": "^9.39.0", - "@wordpress/notices": "^4.16.0", - "@wordpress/private-apis": "^0.30.0", - "@wordpress/url": "^3.49.0" + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/components": "^27.1.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/url": "^3.54.0" }, "engines": { "node": ">=12" @@ -8563,33 +8445,21 @@ "react-dom": "^18.0.0" } }, - "node_modules/@wordpress/reusable-blocks/node_modules/@wordpress/private-apis": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.30.0.tgz", - "integrity": "sha512-mkz2QtbSVNAsFNXBni5XMLV1KYhQAx1vyC5KcEyeQADiRkRUW6XJ+u53WwQfpdjvsEQhkyGpK13Rl7gt3KOpeQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wordpress/rich-text": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.27.0.tgz", - "integrity": "sha512-B7t++SldcI4nb+lO2m9oEdyD8y2FbH5DKY5F2G3xpcEnw4EKSt4SsTzeclMQ/2zzlEHPRKU/IR29SeOIJ1H8JQ==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.30.0.tgz", + "integrity": "sha512-zVc7pRzPajP5M1g79EvLMsvBkduu19TdwbxZrwzHD7xY+jfPdRAzNV8UMw41lVrH203yrAN1tfcJw4Hshjl/VQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.50.0", - "@wordpress/compose": "^6.27.0", - "@wordpress/data": "^9.20.0", - "@wordpress/deprecated": "^3.50.0", - "@wordpress/element": "^5.27.0", - "@wordpress/escape-html": "^2.50.0", - "@wordpress/i18n": "^4.50.0", - "@wordpress/keycodes": "^3.50.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/escape-html": "^2.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/keycodes": "^3.53.0", "memize": "^2.1.0", "rememo": "^4.0.2" }, @@ -8601,15 +8471,15 @@ } }, "node_modules/@wordpress/router": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-0.16.0.tgz", - "integrity": "sha512-IWxELFlzLokckJbP6/NOcojR1OTGx81flQwI2OGZe88Zzjl9yqKXtoKQEA1WA10WKCGNZIncI9QnaCD/mgg8jg==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-0.22.0.tgz", + "integrity": "sha512-f7QQEQ+C2wRDmI7crfkhtrjcKTIS4pCBPEoUwaNYJ4v8Dnc72OvFr5qHwNjG8mHZRoq+VANkcaY8hNsZ3ehN+A==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.24.0", - "@wordpress/private-apis": "^0.29.0", - "@wordpress/url": "^3.48.0", + "@wordpress/element": "^5.30.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/url": "^3.54.0", "history": "^5.1.0" }, "engines": { @@ -8620,24 +8490,24 @@ } }, "node_modules/@wordpress/scripts": { - "version": "26.19.0", - "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-26.19.0.tgz", - "integrity": "sha512-m3QYlgpWRfIqCfU4jWKwGeA12Qkt6d9CMewEIxIBGVlEGd/sL5rU1fM7LKNBEbSPQpaOTWJApNGWPcW75Fwp+w==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-27.4.0.tgz", + "integrity": "sha512-DAX1n/nWtOH77jeHxUFrDiqXGc5OVsDeynyvJOxMMMdi1otN/iO6MkFOg0ExzRXgV4/+8DVpN1RWgeuXLzrBGw==", "dev": true, "dependencies": { "@babel/core": "^7.16.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "^7.32.0", - "@wordpress/browserslist-config": "^5.31.0", - "@wordpress/dependency-extraction-webpack-plugin": "^4.31.0", - "@wordpress/e2e-test-utils-playwright": "^0.16.0", - "@wordpress/eslint-plugin": "^17.5.0", - "@wordpress/jest-preset-default": "^11.19.0", - "@wordpress/npm-package-json-lint-config": "^4.33.0", - "@wordpress/postcss-plugins-preset": "^4.32.0", - "@wordpress/prettier-config": "^3.5.0", - "@wordpress/stylelint-config": "^21.31.0", + "@wordpress/babel-preset-default": "^7.37.0", + "@wordpress/browserslist-config": "^5.36.0", + "@wordpress/dependency-extraction-webpack-plugin": "^5.4.0", + "@wordpress/e2e-test-utils-playwright": "^0.21.0", + "@wordpress/eslint-plugin": "^17.10.0", + "@wordpress/jest-preset-default": "^11.24.0", + "@wordpress/npm-package-json-lint-config": "^4.38.0", + "@wordpress/postcss-plugins-preset": "^4.37.0", + "@wordpress/prettier-config": "^3.10.0", + "@wordpress/stylelint-config": "^21.36.0", "adm-zip": "^0.5.9", "babel-jest": "^29.6.2", "babel-loader": "^8.2.3", @@ -8688,7 +8558,7 @@ "wp-scripts": "bin/wp-scripts.js" }, "engines": { - "node": ">=14", + "node": ">=18", "npm": ">=6.14.4" }, "peerDependencies": { @@ -8990,21 +8860,21 @@ } }, "node_modules/@wordpress/server-side-render": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-4.25.0.tgz", - "integrity": "sha512-ScR5axX8TI6B0YDyZoZJReKSF0muxkx93Qb0buPBv9UxZ/Yv+U3GtNOFZvDZcn6hmHB99qkhuCS0UEVg56isqQ==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-4.30.0.tgz", + "integrity": "sha512-JNT3v2zvdghs7jtNhtQlj0miv+dmwKqyCcZAZ6KLwQEXeU959FmfizV2cRO6cDnPzLEKflOSzQb7y1qF8S6UAQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.45.0", - "@wordpress/blocks": "^12.25.0", - "@wordpress/components": "^25.14.0", - "@wordpress/compose": "^6.25.0", - "@wordpress/data": "^9.18.0", - "@wordpress/deprecated": "^3.48.0", - "@wordpress/element": "^5.25.0", - "@wordpress/i18n": "^4.48.0", - "@wordpress/url": "^3.49.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/url": "^3.54.0", "fast-deep-equal": "^3.1.3" }, "engines": { @@ -9016,9 +8886,9 @@ } }, "node_modules/@wordpress/shortcode": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-3.49.0.tgz", - "integrity": "sha512-4E+CQTj+MWqmYGqyPGUddKX2JgNpMIA6MrTZOQ4MEJp3VIxkLubzIwORfDZ6rlXD8PJ3kvMMivzB1MZ2svnX3Q==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-3.53.0.tgz", + "integrity": "sha512-ste35FEC3wKUmGpPCh0UaujAKUFSamcI2NEW7H+j+ODX8tgsa2fuLX4wtxPenrkoDlCblZVW4Q2tIIgBmex6XA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -9029,9 +8899,9 @@ } }, "node_modules/@wordpress/style-engine": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-1.32.0.tgz", - "integrity": "sha512-0Z3DjiUuwxH9t4P085EFXo+fCT+znOYNwEf59bn6e8jRxlQx7t88ecH8hlzQNswpYj0pKBzXQCUsJsxglZYv3g==", + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-1.36.0.tgz", + "integrity": "sha512-6ANXOxOinWxMssdlhvlGoaI25okwLEx2SC6r+/JH6I7HYlnk/TSSgkpxz9t/b/sGOKrG46KzzXZT2XVb+4pDCQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -9042,9 +8912,9 @@ } }, "node_modules/@wordpress/stylelint-config": { - "version": "21.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.31.0.tgz", - "integrity": "sha512-rorpVMYfFaNWYzg4psfUMpWLkxhD3uwWip6mf96mo/i8De4wxAz6DwKlCPIa4j74SLTiIMrdwXb2qJFNQcjQng==", + "version": "21.36.0", + "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.36.0.tgz", + "integrity": "sha512-P2Bg+Aq0jKR76wmFaNY1a4iInP/+z5+QauPD+StoHksWKvfjkYpqZ3dDLaGHucFDFF6I4UAgsDO8Avt7Q1Tl0w==", "dev": true, "dependencies": { "stylelint-config-recommended": "^6.0.0", @@ -9058,14 +8928,14 @@ } }, "node_modules/@wordpress/sync": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@wordpress/sync/-/sync-0.11.0.tgz", - "integrity": "sha512-690oDaDUYWX3sBeHsOlXyreRFgFzVrb+GO6Vo74lUbx0zdI0sNJeX7blBSn3QvZcysN0cAvCRO1sciJinD4e5A==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@wordpress/sync/-/sync-0.15.0.tgz", + "integrity": "sha512-5pz3MTTYZ30AlpUj1oXHIgyXpWipLMZ5ueqqD+qjvo465/Eok7n7FxJv4IKwlpLG9Z65/znf1CMSO8N9dDxe3w==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", "@types/simple-peer": "^9.11.5", - "@wordpress/url": "^3.50.0", + "@wordpress/url": "^3.54.0", "import-locals": "^2.0.0", "lib0": "^0.2.42", "simple-peer": "^9.11.0", @@ -9079,9 +8949,9 @@ } }, "node_modules/@wordpress/token-list": { - "version": "2.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-2.49.0.tgz", - "integrity": "sha512-TwLvEfkGqztps2xl+J57BYeJzG0lCLV418fem2VXdl2E2BCwt+d/kDggBPb4KmSdRvSO05QukZsRzPsfFRUbug==", + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-2.53.0.tgz", + "integrity": "sha512-SI6/UD8USz6USJUoF9r70/lMtp24tb9qjWCMqJp1vvtFcKgNpqHZ63SXnfE3FlYUt/Y6UurPbbsnXw6FhFEfdw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -9091,22 +8961,22 @@ } }, "node_modules/@wordpress/undo-manager": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-0.9.0.tgz", - "integrity": "sha512-ZD6fVOdDhH8NvV/2fqjkI6W3kURzU7grWMBSZLtnSmSSPdT//1VSIxe0gcbmRvVPWLdj+TXbHifIswcJK0bHhQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-0.13.0.tgz", + "integrity": "sha512-3HY1YuQNmRdRH1ZKKLRaJlqYfWYIiBsq4I1nXKEufmwmHwMHz7nzp+bQe6erutkVran4zMzRILZPsZXpbqm//A==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/is-shallow-equal": "^4.49.0" + "@wordpress/is-shallow-equal": "^4.53.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/url": { - "version": "3.51.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.51.0.tgz", - "integrity": "sha512-OjucjlP1763gfKbe8lv/k3RCisyX8AfNBrhASk7JqxAj6rFhb1ZZO7YmAgB2m+WoGB5v7fkOli0FZyDqISdYyg==", + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.54.0.tgz", + "integrity": "sha512-65/2c3vzzgX4VEd90GG1tbZTN1b10NiqAa2V+a/m0Ak9RoCyAY0MtNNEa4kxCxUyN5ajpgCJCzVJIKDNVj/Fhg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -9117,15 +8987,15 @@ } }, "node_modules/@wordpress/viewport": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-5.24.0.tgz", - "integrity": "sha512-ZFsO1aGR4vW+TW1eiIWc2Nypro32hRhxBSZFEUK565T8a6smTyt1INk9m/lRIShs93w/w3MWiSH3CZW0mzDNlQ==", + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-5.30.0.tgz", + "integrity": "sha512-/OeoHXYVRpeZro3iuNh7HXjZ4HjuE9oVjsaDp1R+4G0NXAmeHd1QJFguUx3j2yrlVz8M5B0EsB7XJ4pkczMW9g==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/compose": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/element": "^5.24.0" + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0" }, "engines": { "node": ">=12" @@ -9135,32 +9005,32 @@ } }, "node_modules/@wordpress/warning": { - "version": "2.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.50.0.tgz", - "integrity": "sha512-y7Zf48roDfiPgbRAWGXDwN3C8sfbEdneGq+HvXCW6rIeGYnDLdEkpX9i7RfultkFFPVeSP3FpMKVMkto2nbqzA==", + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.53.0.tgz", + "integrity": "sha512-53O09aUJgEuGcCVTHQcxvqjeU79rHF6fw9VSZwv6lYfZTwwtxwMHGPF6hUp12NeR+bqYGsUz2Ls6gzSHaAE2Zw==", "dev": true, "engines": { "node": ">=12" } }, "node_modules/@wordpress/widgets": { - "version": "3.24.0", - "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-3.24.0.tgz", - "integrity": "sha512-bgjUoBjHKhyM2u7QrTScll7hCFDrHw0OxZWGbPXOGfE0VUgaej/d8QV5re7I+sOIi0g8+XLYQE0fwEyANt1iUg==", + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-3.30.0.tgz", + "integrity": "sha512-D56SyxK2U3OzNVy3HqfixnbWpJa9cKAaZyuK742YbNDQCHL8CgVvhHZAV4ROvi6Nk+/xtC/ndNy/WLeOKS6ROA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.44.0", - "@wordpress/block-editor": "^12.15.0", - "@wordpress/blocks": "^12.24.0", - "@wordpress/components": "^25.13.0", - "@wordpress/compose": "^6.24.0", - "@wordpress/core-data": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/element": "^5.24.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/notices": "^4.15.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/notices": "^4.21.0", "classnames": "^2.3.1" }, "peerDependencies": { @@ -9169,9 +9039,9 @@ } }, "node_modules/@wordpress/wordcount": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/wordcount/-/wordcount-3.50.0.tgz", - "integrity": "sha512-lRfIX3B9ha//bqsUihym2BnOiAsdDQr22vdy0wZIpm5G2tFvTddCKHy0YClf52IJK0z61WqbNuF9hrvzWWxL+g==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/wordcount/-/wordcount-3.53.0.tgz", + "integrity": "sha512-pzx1VojKb/yh/J+GOb8+QF3UwlXuIaSXG5wurABxXPBZVk8UBmZotiEvQOZRJW1J6wn8Kta6eiwn34imR4la9A==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -9675,9 +9545,9 @@ "dev": true }, "node_modules/autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "version": "10.4.18", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", + "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", "dev": true, "funding": [ { @@ -9694,9 +9564,9 @@ } ], "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001591", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -10169,12 +10039,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/body-scroll-lock": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/body-scroll-lock/-/body-scroll-lock-3.1.5.tgz", - "integrity": "sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg==", - "dev": true - }, "node_modules/bonjour-service": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", @@ -10228,9 +10092,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -10247,10 +10111,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -10341,9 +10205,9 @@ } }, "node_modules/builtins/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -10504,9 +10368,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001538", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", - "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", + "version": "1.0.30001596", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz", + "integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==", "dev": true, "funding": [ { @@ -12034,14 +11898,14 @@ } }, "node_modules/deepsignal": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.3.6.tgz", - "integrity": "sha512-yjd+vtiznL6YaMptOsKnEKkPr60OEApa+LRe+Qe6Ile/RfCOrELKk/YM3qVpXFZiyOI3Ng67GDEyjAlqVc697g==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.5.0.tgz", + "integrity": "sha512-bFywDpBUUWMs576H2dgLFLLFuQ/UWXbzHfKD98MZTfGsl7+twIzvz4ihCNrRrZ/Emz3kqJaNIAp5eBWUEWhnAw==", "dev": true, "peerDependencies": { "@preact/signals": "^1.1.4", - "@preact/signals-core": "^1.3.1", - "@preact/signals-react": "^1.3.3", + "@preact/signals-core": "^1.5.1", + "@preact/signals-react": "^1.3.8 || ^2.0.0", "preact": "^10.16.0" }, "peerDependenciesMeta": { @@ -12562,12 +12426,6 @@ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true }, - "node_modules/dom-scroll-into-view": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-1.2.1.tgz", - "integrity": "sha512-LwNVg3GJOprWDO+QhLL1Z9MMgWe/KAFLxVWKzjRTxNSPn8/LLDIfmuG71YHznXCqaqTjvHJDYO1MEAgX6XCNbQ==", - "dev": true - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -12686,9 +12544,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.508", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.508.tgz", - "integrity": "sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg==", + "version": "1.4.698", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.698.tgz", + "integrity": "sha512-f9iZD1t3CLy1AS6vzM5EKGa6p9pRcOeEFXRFbaG2Ta+Oe7MkfRQ3fsvPYidzHe1h4i0JvIvpcY55C+B6BZNGtQ==", "dev": true }, "node_modules/emittery": { @@ -13195,9 +13053,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "27.6.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.3.tgz", - "integrity": "sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA==", + "version": "27.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz", + "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==", "dev": true, "dependencies": { "@typescript-eslint/utils": "^5.10.0" @@ -13206,7 +13064,7 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0", + "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0", "eslint": "^7.0.0 || ^8.0.0", "jest": "*" }, @@ -15343,9 +15201,9 @@ } }, "node_modules/http-link-header": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.1.1.tgz", - "integrity": "sha512-mW3N/rTYpCn99s1do0zx6nzFZSwLH9HGfUM4ZqLWJ16ylmYaC2v5eYGqrNTQlByx8AzUgGI+V/32gXPugs1+Sw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.1.2.tgz", + "integrity": "sha512-6qz1XhMq/ryde52SZGzVhzi3jcG2KqO16KITkupyQxvW6u7iylm0Fq7r3OpCYsc0S0ELlCiFpuxDcccUwjbEqA==", "dev": true, "engines": { "node": ">=6.0.0" @@ -15445,15 +15303,15 @@ } }, "node_modules/husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", + "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", "dev": true, "bin": { - "husky": "lib/bin.js" + "husky": "bin.mjs" }, "engines": { - "node": ">=14" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -15796,9 +15654,9 @@ } }, "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "dev": true }, "node_modules/ipaddr.js": { @@ -19723,14 +19581,15 @@ } }, "node_modules/lib0": { - "version": "0.2.88", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.88.tgz", - "integrity": "sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ==", + "version": "0.2.91", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.91.tgz", + "integrity": "sha512-LRcTp8RmdHexL8olb7qErdROnJ2L6Js5Am99WQo0hiTRDWtk6vyUJJjTB6I/RcW8jwNQshM3NqH598DdhSOajA==", "dev": true, "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", "0gentesthtml": "bin/gentesthtml.js", "0serve": "bin/0serve.js" }, @@ -20072,8 +19931,7 @@ "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "node_modules/lodash.escape": { "version": "4.0.1", @@ -21009,9 +20867,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/normalize-package-data": { @@ -21042,9 +20900,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -21248,9 +21106,9 @@ } }, "node_modules/npm-package-json-lint/node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/npm-package-json-lint/node_modules/lru-cache": { @@ -21266,9 +21124,9 @@ } }, "node_modules/npm-package-json-lint/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -22201,13 +22059,13 @@ } }, "node_modules/playwright": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", - "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz", + "integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==", "dev": true, "peer": true, "dependencies": { - "playwright-core": "1.40.1" + "playwright-core": "1.42.1" }, "bin": { "playwright": "cli.js" @@ -22247,9 +22105,9 @@ } }, "node_modules/playwright/node_modules/playwright-core": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", - "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz", + "integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==", "dev": true, "peer": true, "bin": { @@ -22918,9 +22776,9 @@ "dev": true }, "node_modules/preact": { - "version": "10.19.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.2.tgz", - "integrity": "sha512-UA9DX/OJwv6YwP9Vn7Ti/vF80XL+YA5H2l7BpCtUr3ya8LWHFzpiO5R+N7dN16ujpIxhekRFuOOF82bXX7K/lg==", + "version": "10.19.6", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.6.tgz", + "integrity": "sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==", "dev": true, "funding": { "type": "opencollective", @@ -22937,9 +22795,9 @@ } }, "node_modules/prettier": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", - "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -23626,62 +23484,6 @@ "node": ">=8.10.0" } }, - "node_modules/reakit": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/reakit/-/reakit-1.3.11.tgz", - "integrity": "sha512-mYxw2z0fsJNOQKAEn5FJCPTU3rcrY33YZ/HzoWqZX0G7FwySp1wkCYW79WhuYMNIUFQ8s3Baob1RtsEywmZSig==", - "dev": true, - "dependencies": { - "@popperjs/core": "^2.5.4", - "body-scroll-lock": "^3.1.5", - "reakit-system": "^0.15.2", - "reakit-utils": "^0.15.2", - "reakit-warning": "^0.6.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ariakit" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/reakit/node_modules/reakit-system": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/reakit-system/-/reakit-system-0.15.2.tgz", - "integrity": "sha512-TvRthEz0DmD0rcJkGamMYx+bATwnGNWJpe/lc8UV2Js8nnPvkaxrHk5fX9cVASFrWbaIyegZHCWUBfxr30bmmA==", - "dev": true, - "dependencies": { - "reakit-utils": "^0.15.2" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/reakit/node_modules/reakit-utils": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/reakit-utils/-/reakit-utils-0.15.2.tgz", - "integrity": "sha512-i/RYkq+W6hvfFmXw5QW7zvfJJT/K8a4qZ0hjA79T61JAFPGt23DsfxwyBbyK91GZrJ9HMrXFVXWMovsKBc1qEQ==", - "dev": true, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/reakit/node_modules/reakit-warning": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/reakit-warning/-/reakit-warning-0.6.2.tgz", - "integrity": "sha512-z/3fvuc46DJyD3nJAUOto6inz2EbSQTjvI/KBQDqxwB0y02HDyeP8IWOJxvkuAUGkWpeSx+H3QWQFSNiPcHtmw==", - "dev": true, - "dependencies": { - "reakit-utils": "^0.15.2" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0" - } - }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -24987,9 +24789,9 @@ } }, "node_modules/socks/node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", "dev": true }, "node_modules/source-map": { @@ -26444,9 +26246,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -26567,9 +26369,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { @@ -26922,9 +26724,9 @@ } }, "node_modules/web-vitals": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.0.tgz", - "integrity": "sha512-f5YnCHVG9Y6uLCePD4tY8bO/Ge15NPEQWtvm3tPzDKygloiqtb4SVqRHBcrIAqo2ztqX5XueqDn97zHF0LdT6w==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.2.tgz", + "integrity": "sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg==", "dev": true }, "node_modules/webidl-conversions": { @@ -27833,9 +27635,9 @@ } }, "node_modules/yjs": { - "version": "13.6.11", - "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.11.tgz", - "integrity": "sha512-FvRRJKX9u270dOLkllGF/UDCWwmIv2Z+ucM4v1QO1TuxdmoiMnSUXH1HAcOKOrkBEhQtPTkxep7tD2DrQB+l0g==", + "version": "13.6.14", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.14.tgz", + "integrity": "sha512-D+7KcUr0j+vBCUSKXXEWfA+bG4UQBviAwP3gYBhkstkgwy5+8diOPMx0iqLIOxNo/HxaREUimZRxqHGAHCL2BQ==", "dev": true, "dependencies": { "lib0": "^0.2.86" @@ -29437,22 +29239,30 @@ } }, "@floating-ui/dom": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.1.tgz", - "integrity": "sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "dev": true, "requires": { - "@floating-ui/core": "^1.4.1", - "@floating-ui/utils": "^0.1.1" + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + }, + "dependencies": { + "@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==", + "dev": true + } } }, "@floating-ui/react-dom": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", - "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "dev": true, "requires": { - "@floating-ui/dom": "^1.5.1" + "@floating-ui/dom": "^1.6.1" } }, "@floating-ui/utils": { @@ -30668,13 +30478,13 @@ } }, "@playwright/test": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", - "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz", + "integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==", "dev": true, "peer": true, "requires": { - "playwright": "1.40.1" + "playwright": "1.42.1" } }, "@pmmmwh/react-refresh-webpack-plugin": { @@ -30708,12 +30518,6 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, - "@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "dev": true - }, "@preact/signals": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.2.2.tgz", @@ -30724,9 +30528,9 @@ } }, "@preact/signals-core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.5.0.tgz", - "integrity": "sha512-U2diO1Z4i1n2IoFgMYmRdHWGObNrcuTRxyNEn7deSq2cru0vj0583HYQZHsAqcs7FE+hQyX3mjIV7LAfHCvy8w==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.5.1.tgz", + "integrity": "sha512-dE6f+WCX5ZUDwXzUIWNMhhglmuLpqJhuy3X3xHrhZYI0Hm2LyQwOu0l9mdPiWrVNsE+Q7txOnJPgtIqHCYoBVA==", "dev": true }, "@puppeteer/browsers": { @@ -31486,9 +31290,9 @@ } }, "@testing-library/jest-dom": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.2.0.tgz", - "integrity": "sha512-+BVQlJ9cmEn5RDMUS8c2+TU6giLvzaHZ8sU/x0Jj7fk+6/46wPdwlgOPcpxS17CjcanBi/3VmGMqVr2rmbUmNw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.2.tgz", + "integrity": "sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==", "dev": true, "requires": { "@adobe/css-tools": "^4.3.2", @@ -31559,9 +31363,9 @@ } }, "@testing-library/react": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz", - "integrity": "sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.2.1.tgz", + "integrity": "sha512-sGdjws32ai5TLerhvzThYFbpnF9XtL65Cjf+gB0Dhr29BGqK+mAeN7SURSdu+eqgET4ANcWoC7FQpkaiGvBr+A==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", @@ -31811,9 +31615,9 @@ } }, "@types/jest": { - "version": "29.5.11", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.11.tgz", - "integrity": "sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==", + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", "dev": true, "requires": { "expect": "^29.0.0", @@ -31893,6 +31697,21 @@ "@types/node": "*" } }, + "@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "dev": true + }, + "@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -32146,9 +31965,9 @@ } }, "@types/wordpress__block-editor": { - "version": "11.5.9", - "resolved": "https://registry.npmjs.org/@types/wordpress__block-editor/-/wordpress__block-editor-11.5.9.tgz", - "integrity": "sha512-yIFKaL40j/dDIDOH5hlvlTwwuTqb1SDgVYZQ8v498fAq+tYRQKZCJICpAunSmlMQpxHX1qs1+ZqmljyWvU1p4w==", + "version": "11.5.11", + "resolved": "https://registry.npmjs.org/@types/wordpress__block-editor/-/wordpress__block-editor-11.5.11.tgz", + "integrity": "sha512-NyEXf5QIbNTFd4rKRwuQ07PPqvok8ntb81+IHLXDzAxX1uWxOng98uwDhoordeNB1md8ab2iIo08KR2VXq2FNg==", "dev": true, "requires": { "@types/react": "*", @@ -32189,9 +32008,9 @@ } }, "@types/wordpress__edit-post": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@types/wordpress__edit-post/-/wordpress__edit-post-7.5.5.tgz", - "integrity": "sha512-+T3eHrlJ1cWNoSqcTNLN+AfcAEAf/CLIGAiHvcM0wDSJ/nAmZbZKbWAynh2/zZgd90DqKeKeOXKY2JK9cbtoAA==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/wordpress__edit-post/-/wordpress__edit-post-7.5.6.tgz", + "integrity": "sha512-76G8fCTQb+NduBXTr8QjZpEs/w/Nbfy41G4xC2YpMsqw6vV0+g1KlQnfXWc3y0JyeCM9nz0/6dG4xl34hOFTTA==", "dev": true, "requires": { "@types/react": "*", @@ -32616,16 +32435,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.0.tgz", - "integrity": "sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/type-utils": "6.19.0", - "@typescript-eslint/utils": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -32635,28 +32454,28 @@ }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz", - "integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "requires": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" } }, "@typescript-eslint/types": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", - "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true }, "@typescript-eslint/visitor-keys": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", - "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "requires": { - "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, @@ -32710,31 +32529,31 @@ } }, "@typescript-eslint/type-utils": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.0.tgz", - "integrity": "sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "6.19.0", - "@typescript-eslint/utils": "6.19.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "dependencies": { "@typescript-eslint/types": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", - "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz", - "integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "requires": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -32744,12 +32563,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", - "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "requires": { - "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, @@ -32781,9 +32600,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -32845,44 +32664,44 @@ } }, "@typescript-eslint/utils": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.0.tgz", - "integrity": "sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.19.0", - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/typescript-estree": "6.19.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz", - "integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "requires": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" } }, "@typescript-eslint/types": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz", - "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz", - "integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "requires": { - "@typescript-eslint/types": "6.19.0", - "@typescript-eslint/visitor-keys": "6.19.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -32892,12 +32711,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz", - "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "requires": { - "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, @@ -32929,9 +32748,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -33138,47 +32957,47 @@ "requires": {} }, "@wordpress/a11y": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.50.0.tgz", - "integrity": "sha512-eQiPGnxqiL1LgnHztFG0RGSFZ5phwR8B8Fr4lbJsFalsc9R/tOcjewvf2KN0yi2UlRA5ssAeiTP+tYmeAqtOHQ==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.53.0.tgz", + "integrity": "sha512-8Fg3c21oO0J6MjFb3UJ2pmDvwXLK9WVn2RdohqG36o3ft/oGD0FTtUw5ct5sgaCUcydFrCN7iNLJ/MLgbSrFiw==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/dom-ready": "^3.50.0", - "@wordpress/i18n": "^4.50.0" + "@wordpress/dom-ready": "^3.53.0", + "@wordpress/i18n": "^4.53.0" } }, "@wordpress/api-fetch": { - "version": "6.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.47.0.tgz", - "integrity": "sha512-NA/jWDXoVtJmiVBYhlxts2UrgKJpJM+zTGzLCfRQCZUzpJYm3LonB8x+uCQ78nEyxCY397Esod3jnbquYjOr0Q==", + "version": "6.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.50.0.tgz", + "integrity": "sha512-a459l9WD58W5UyQkD6c54+4hv2hZzstDSzoJRMOZGSeEfbgN+49vgHLNEVhDHjNsS7Z6X2KeyyR/YoRgtXfloA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.50.0", - "@wordpress/url": "^3.51.0" + "@wordpress/i18n": "^4.53.0", + "@wordpress/url": "^3.54.0" } }, "@wordpress/autop": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-3.49.0.tgz", - "integrity": "sha512-bc0jUu8yOCioNFFgrO++XhdGU6QpL9HF9LeWxzayqp5Br4z9z7Zslp+KH1Gy6H2RNowEr8Fq4hZ7JwQ009EDmw==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-3.53.0.tgz", + "integrity": "sha512-t330lnDM8gb8G4U8Ky1qWvDxDsNn4FP+QVTrN72AAhjsz95VTQRsNY5xesedEN82e6FRdPIoeHyd/RuVqd6QTg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/babel-plugin-import-jsx-pragma": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.33.0.tgz", - "integrity": "sha512-CjzruFKWgzU/mO/nnQJ2l9UlzZQpqS60UC6l2vNdJ9oD2nKHR5Oou6kNic3QhWDVJrBf2JUiJJ0TC280bykXmA==", + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.36.0.tgz", + "integrity": "sha512-xgBy9HnA0xL5e0Ipku7Ga3QimrfwTQ3njnN79mT8wNcim2APIlyiWSG3GndTdPoSGdrxGPv2ZrpqBdKsiGzoWQ==", "dev": true, "requires": {} }, "@wordpress/babel-preset-default": { - "version": "7.34.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.34.0.tgz", - "integrity": "sha512-yjFOllyTktFHtcIEgU3ghXBn8lItzr5mPLf0xdSpe0cHceFYL1hT1oprhgRL+olZweaO96Yfm0qUCCKQfJBWsA==", + "version": "7.37.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.37.0.tgz", + "integrity": "sha512-XE9NUIoc428MHP3p6DMNjRV4Df97K9JHkzXwOwJjjHp00ce2ckh4wSkZh287Zi1X+uNcrROERtSp4jjWHUhvHA==", "dev": true, "requires": { "@babel/core": "^7.16.0", @@ -33187,74 +33006,73 @@ "@babel/preset-env": "^7.16.0", "@babel/preset-typescript": "^7.16.0", "@babel/runtime": "^7.16.0", - "@wordpress/babel-plugin-import-jsx-pragma": "^4.33.0", - "@wordpress/browserslist-config": "^5.33.0", - "@wordpress/warning": "^2.50.0", + "@wordpress/babel-plugin-import-jsx-pragma": "^4.36.0", + "@wordpress/browserslist-config": "^5.36.0", + "@wordpress/warning": "^2.53.0", "browserslist": "^4.21.10", "core-js": "^3.31.0", "react": "^18.2.0" } }, "@wordpress/base-styles": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.39.0.tgz", - "integrity": "sha512-Obc6fKAnqzuWQSLgoce2yxhwMLd0nu4j7k3pVkBSzuitPw1LokmzHcHWPpgpMGR1T8CzKuj0Wsybcr2n3Xtyug==", + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.44.0.tgz", + "integrity": "sha512-Kgn5WsCmY1GPUhMQaUGSL8MqVUrstjYYel8PjAEo5VmKPICOaMBrip5dwy7zTomX4fj+sdV1NLIJJ6Bqi5zxnw==", "dev": true }, "@wordpress/blob": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-3.49.0.tgz", - "integrity": "sha512-HYPMuXJx35uYlQC6JF9XXvPsOht2X8qJfXzGtxWb51OIC6DSRqh3f6s12fgPaNh9uElcSjQ4+Su286upu7S4vw==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/blob/-/blob-3.53.0.tgz", + "integrity": "sha512-fB1oXibUBfL2eTt303nbkbIJPP+SkKDGxEbYNIBrwaAoqp3oma7Q5uhguI8XFwKcdFw5I73U9bhlnnLMhK4FuA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/block-editor": { - "version": "12.17.0", - "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-12.17.0.tgz", - "integrity": "sha512-np1ICMmScrSuDOQRYQqlDY35kOoQEHuckSCjJPQpjprutXaqG+Jk+RAeeHVgQ8Ze5B+QgkFLjNvYwRh11kYdqg==", + "version": "12.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-12.21.0.tgz", + "integrity": "sha512-B6c8YNWyv/zZPoEIo+Ks1W/RQQ9InUf99uZqlZSSevjDaKXZgFWWtecto60b8JQIqmpQJ32Y7LjdHcTMgAcFVQ==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "^3.49.0", - "@wordpress/api-fetch": "^6.46.0", - "@wordpress/blob": "^3.49.0", - "@wordpress/blocks": "^12.26.0", - "@wordpress/commands": "^0.20.0", - "@wordpress/components": "^25.15.0", - "@wordpress/compose": "^6.26.0", - "@wordpress/data": "^9.19.0", - "@wordpress/date": "^4.49.0", - "@wordpress/deprecated": "^3.49.0", - "@wordpress/dom": "^3.49.0", - "@wordpress/element": "^5.26.0", - "@wordpress/escape-html": "^2.49.0", - "@wordpress/hooks": "^3.49.0", - "@wordpress/html-entities": "^3.49.0", - "@wordpress/i18n": "^4.49.0", - "@wordpress/icons": "^9.40.0", - "@wordpress/is-shallow-equal": "^4.49.0", - "@wordpress/keyboard-shortcuts": "^4.26.0", - "@wordpress/keycodes": "^3.49.0", - "@wordpress/notices": "^4.17.0", - "@wordpress/preferences": "^3.26.0", - "@wordpress/private-apis": "^0.31.0", - "@wordpress/rich-text": "^6.26.0", - "@wordpress/style-engine": "^1.32.0", - "@wordpress/token-list": "^2.49.0", - "@wordpress/url": "^3.50.0", - "@wordpress/warning": "^2.49.0", - "@wordpress/wordcount": "^3.49.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/blob": "^3.53.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/commands": "^0.24.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/date": "^4.53.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/escape-html": "^2.53.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/keyboard-shortcuts": "^4.30.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/preferences": "^3.30.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/style-engine": "^1.36.0", + "@wordpress/token-list": "^2.53.0", + "@wordpress/url": "^3.54.0", + "@wordpress/warning": "^2.53.0", + "@wordpress/wordcount": "^3.53.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", "deepmerge": "^4.3.0", "diff": "^4.0.2", - "dom-scroll-into-view": "^1.2.1", "fast-deep-equal": "^3.1.3", "memize": "^2.1.0", "postcss": "^8.4.21", @@ -33264,75 +33082,47 @@ "react-easy-crop": "^4.5.1", "rememo": "^4.0.2", "remove-accents": "^0.5.0" - }, - "dependencies": { - "@wordpress/commands": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.20.0.tgz", - "integrity": "sha512-aQQCr3ViLwPEo/SEeW7FowA4zCfvypkO7eqTuTlcd+1E3ndRzlWA91rneo+l9GBUQ/elZzhc5Z0i2cMxHTMDRQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.15.0", - "@wordpress/data": "^9.19.0", - "@wordpress/element": "^5.26.0", - "@wordpress/i18n": "^4.49.0", - "@wordpress/icons": "^9.40.0", - "@wordpress/keyboard-shortcuts": "^4.26.0", - "@wordpress/private-apis": "^0.31.0", - "classnames": "^2.3.1", - "cmdk": "^0.2.0", - "rememo": "^4.0.2" - } - }, - "@wordpress/private-apis": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.31.0.tgz", - "integrity": "sha512-Hx2LJfkgbeAixXHDvi/rBly4+mShhrJfYXwyh6uTLnXkjp6OcPuBbCXhIfARw45lNdiqWdHoqXcAl1RTBFFd4g==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0" - } - } } }, "@wordpress/block-library": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-8.24.0.tgz", - "integrity": "sha512-dztSfFa6S6QjlwS477HhPtjLHUc9UpC/qr2UFRvLA7euYdiEEIlYIHH/JlD9VHXJfQb5eA9lr9f3jMulU3PeSw==", + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-8.30.0.tgz", + "integrity": "sha512-7AC9/hvjhE6tGq2nL61oiCs5x/dt6Lcaj9ZL7/5Au5vkIlqOETt+9bECQmJEAYHh+PNo/jO7nX5CaEA5bRrP2Q==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.47.0", - "@wordpress/api-fetch": "^6.44.0", - "@wordpress/autop": "^3.47.0", - "@wordpress/blob": "^3.47.0", - "@wordpress/block-editor": "^12.15.0", - "@wordpress/blocks": "^12.24.0", - "@wordpress/components": "^25.13.0", - "@wordpress/compose": "^6.24.0", - "@wordpress/core-data": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/date": "^4.47.0", - "@wordpress/deprecated": "^3.47.0", - "@wordpress/dom": "^3.47.0", - "@wordpress/element": "^5.24.0", - "@wordpress/escape-html": "^2.47.0", - "@wordpress/hooks": "^3.47.0", - "@wordpress/html-entities": "^3.47.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/interactivity": "^3.0.0", - "@wordpress/keycodes": "^3.47.0", - "@wordpress/notices": "^4.15.0", - "@wordpress/primitives": "^3.45.0", - "@wordpress/private-apis": "^0.29.0", - "@wordpress/reusable-blocks": "^4.24.0", - "@wordpress/rich-text": "^6.24.0", - "@wordpress/server-side-render": "^4.24.0", - "@wordpress/url": "^3.48.0", - "@wordpress/viewport": "^5.24.0", - "@wordpress/wordcount": "^3.47.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/autop": "^3.53.0", + "@wordpress/blob": "^3.53.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/date": "^4.53.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/escape-html": "^2.53.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/interactivity": "^5.2.0", + "@wordpress/interactivity-router": "^1.3.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/patterns": "^1.14.0", + "@wordpress/primitives": "^3.51.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/reusable-blocks": "^4.30.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/server-side-render": "^4.30.0", + "@wordpress/url": "^3.54.0", + "@wordpress/viewport": "^5.30.0", + "@wordpress/wordcount": "^3.53.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", @@ -33353,36 +33143,36 @@ } }, "@wordpress/block-serialization-default-parser": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-4.49.0.tgz", - "integrity": "sha512-9pQ6yxOhiFv+47iZWF3Te6N+PK+IFlEWgG3IpSIj3mWV6OI7FoM/+C2ePeR06OxE2cQHRkL9pAsECtK9eDJmCQ==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-4.53.0.tgz", + "integrity": "sha512-EfLBKT6igcuS8NnnFM3IAIefJJm5ooR5M8+ZnsMYQLgCnpQ8fikCs2r2UwBXxQ8DqONmMrXnORAld2o8C21dxw==", "dev": true, "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/blocks": { - "version": "12.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-12.26.0.tgz", - "integrity": "sha512-iIWUJmxGPXymf+X1rlHT0QxHV8+NzLfe96S3oKpX2UyFc/5H+eYWwyhA7u2S3kam/ss1DwAwdS7rRIMUHPU5PQ==", + "version": "12.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-12.30.0.tgz", + "integrity": "sha512-XBuT+I15TGA7B8AFE13W8CcXvfAIzu1w9V7NRKZZm8A7TCN4BTUSGUwufbd8Jw7qZ7yrh8+JwzHtBOL2GpBByw==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/autop": "^3.49.0", - "@wordpress/blob": "^3.49.0", - "@wordpress/block-serialization-default-parser": "^4.49.0", - "@wordpress/compose": "^6.26.0", - "@wordpress/data": "^9.19.0", - "@wordpress/deprecated": "^3.49.0", - "@wordpress/dom": "^3.49.0", - "@wordpress/element": "^5.26.0", - "@wordpress/hooks": "^3.49.0", - "@wordpress/html-entities": "^3.49.0", - "@wordpress/i18n": "^4.49.0", - "@wordpress/is-shallow-equal": "^4.49.0", - "@wordpress/private-apis": "^0.31.0", - "@wordpress/rich-text": "^6.26.0", - "@wordpress/shortcode": "^3.49.0", + "@wordpress/autop": "^3.53.0", + "@wordpress/blob": "^3.53.0", + "@wordpress/block-serialization-default-parser": "^4.53.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/shortcode": "^3.53.0", "change-case": "^4.1.2", "colord": "^2.7.0", "fast-deep-equal": "^3.1.3", @@ -33397,15 +33187,6 @@ "uuid": "^9.0.1" }, "dependencies": { - "@wordpress/private-apis": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.31.0.tgz", - "integrity": "sha512-Hx2LJfkgbeAixXHDvi/rBly4+mShhrJfYXwyh6uTLnXkjp6OcPuBbCXhIfARw45lNdiqWdHoqXcAl1RTBFFd4g==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0" - } - }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -33421,34 +33202,34 @@ } }, "@wordpress/browserslist-config": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.33.0.tgz", - "integrity": "sha512-dv1ZlpqGk8gaSBJPP/Z/1uOuxjtP0EBsHVKInLRu6FWLTJkK8rnCeC3xJT3/2TtJ0rasLC79RoytfhXTOODVwg==", + "version": "5.36.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.36.0.tgz", + "integrity": "sha512-D4Y+MhZHAW4mDNFxHGacVpZgOmkkL9k5+TuVchC8cVSdpAt0VSkzKsXAumoQuEYUXyio/NMkhnU153FO+ci3cQ==", "dev": true }, "@wordpress/commands": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.18.0.tgz", - "integrity": "sha512-qJyAz2WtpRcJIKWtdkI5wWAnjx5aU9NdsZNW59xf9k9Uh3N1+1dvfFl3FJpR3pGCJv3dmuyFaWXJNYXqswXj/w==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.24.0.tgz", + "integrity": "sha512-siX+ouT9yvcdVYMdSY3REs3Tmnnzkv4L/dBhgJBrjJeMqh8badHR/4yqGEprPxuoRrU+Or5pwQDgq+HsvlxiaA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.13.0", - "@wordpress/data": "^9.17.0", - "@wordpress/element": "^5.24.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/keyboard-shortcuts": "^4.24.0", - "@wordpress/private-apis": "^0.29.0", + "@wordpress/components": "^27.1.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/keyboard-shortcuts": "^4.30.0", + "@wordpress/private-apis": "^0.35.0", "classnames": "^2.3.1", "cmdk": "^0.2.0", "rememo": "^4.0.2" } }, "@wordpress/components": { - "version": "25.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-25.16.0.tgz", - "integrity": "sha512-voQuMsO5JbH+JW33TnWurwwvpSb8IQ4XU5wyVMubX4TUwadt+/2ToNJbZIDXoaJPei7vbM81Ft+pH+zGlN8CyA==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-27.1.0.tgz", + "integrity": "sha512-5y7xr4vmWqVzErM/BHKnOTcZpFkfPdhNJkRMXztL8NP6EcrkSPRexiUtaCTQqbNRKC/ZBtwR6P93JKKd8soKlQ==", "dev": true, "requires": { "@ariakit/react": "^0.3.12", @@ -33459,33 +33240,32 @@ "@emotion/serialize": "^1.0.2", "@emotion/styled": "^11.6.0", "@emotion/utils": "^1.0.0", - "@floating-ui/react-dom": "^2.0.1", + "@floating-ui/react-dom": "^2.0.8", "@types/gradient-parser": "0.1.3", "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.2.24", - "@wordpress/a11y": "^3.50.0", - "@wordpress/compose": "^6.27.0", - "@wordpress/date": "^4.50.0", - "@wordpress/deprecated": "^3.50.0", - "@wordpress/dom": "^3.50.0", - "@wordpress/element": "^5.27.0", - "@wordpress/escape-html": "^2.50.0", - "@wordpress/hooks": "^3.50.0", - "@wordpress/html-entities": "^3.50.0", - "@wordpress/i18n": "^4.50.0", - "@wordpress/icons": "^9.41.0", - "@wordpress/is-shallow-equal": "^4.50.0", - "@wordpress/keycodes": "^3.50.0", - "@wordpress/primitives": "^3.48.0", - "@wordpress/private-apis": "^0.32.0", - "@wordpress/rich-text": "^6.27.0", - "@wordpress/warning": "^2.50.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/date": "^4.53.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/escape-html": "^2.53.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/primitives": "^3.51.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/warning": "^2.53.0", "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", "date-fns": "^2.28.0", "deepmerge": "^4.3.0", - "dom-scroll-into-view": "^1.2.1", "downshift": "^6.0.15", "fast-deep-equal": "^3.1.3", "framer-motion": "^10.13.0", @@ -33496,22 +33276,12 @@ "path-to-regexp": "^6.2.1", "re-resizable": "^6.4.0", "react-colorful": "^5.3.1", - "reakit": "^1.3.11", "remove-accents": "^0.5.0", "use-lilius": "^2.0.1", "uuid": "^9.0.1", "valtio": "1.7.0" }, "dependencies": { - "@wordpress/private-apis": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.32.0.tgz", - "integrity": "sha512-P7nxI/bGMDQhtlTfSe1Y2SDfrd20K5UMnTHbq+hmIkzBGRpNPbdGeNu2bZaZtIvmXk1OCR0Fkef+e6QqkOfYPg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0" - } - }, "uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -33521,79 +33291,68 @@ } }, "@wordpress/compose": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-6.27.0.tgz", - "integrity": "sha512-jbEQQ2znRyJTwUNR4m5BKaDyIsuK9TMZx0SKqP+FTfGqT3y7scOnQrHpK0kZdPji++/1cBbn3gSPBLCEmtmHRw==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-6.30.0.tgz", + "integrity": "sha512-YPyO0Ms3HUZSxb0ICzGe/55IWXAvzk0+iT4r3fiRtJaxrYtwz7XvLHkIJQ25bbue76lJbn4xFVD6hk30DiE7mA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "^3.50.0", - "@wordpress/dom": "^3.50.0", - "@wordpress/element": "^5.27.0", - "@wordpress/is-shallow-equal": "^4.50.0", - "@wordpress/keycodes": "^3.50.0", - "@wordpress/priority-queue": "^2.50.0", - "@wordpress/undo-manager": "^0.10.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/priority-queue": "^2.53.0", + "@wordpress/undo-manager": "^0.13.0", "change-case": "^4.1.2", "clipboard": "^2.0.11", "mousetrap": "^1.6.5", "use-memo-one": "^1.1.1" - }, - "dependencies": { - "@wordpress/undo-manager": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-0.10.0.tgz", - "integrity": "sha512-ODDqAL6BSvD+J7FV+sQTAaVHiPChh/4KBnKg8pb2ogg+Weq6VynthxDxGpQnN8FcMKB9ZoyS3SNIl8pVXLKIwA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0", - "@wordpress/is-shallow-equal": "^4.50.0" - } - } } }, "@wordpress/core-commands": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-0.16.0.tgz", - "integrity": "sha512-vo/hGAEzhiXpcVnd7sHPeDmod3JuNBPanRCUK5z440H6CEHGg0GuWP84pi34FXkvbpvoX9HWTF+5yl61GlXebw==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-0.22.0.tgz", + "integrity": "sha512-MclwrLXt9Lb9Fa4carfatguJZnlb1DqWjy2v7yYJtTB8QutcW1g/8pojkqdzNjcrr5y5vX2LgtAu7o8mSbX20A==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^12.15.0", - "@wordpress/commands": "^0.18.0", - "@wordpress/core-data": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/element": "^5.24.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/private-apis": "^0.29.0", - "@wordpress/router": "^0.16.0", - "@wordpress/url": "^3.48.0" + "@wordpress/block-editor": "^12.21.0", + "@wordpress/commands": "^0.24.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/router": "^0.22.0", + "@wordpress/url": "^3.54.0" } }, "@wordpress/core-data": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-6.26.0.tgz", - "integrity": "sha512-RI3uf3gHnjNyHgMm72IQlk0k83FJAYmLOGUJM01NuMvsVIxDxp03rfvy3lCfNy1+BknknOYFhUaX88NKrizgNA==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-6.30.0.tgz", + "integrity": "sha512-LVqRcyGwOHAgdtSNDiW5qkmjxCbatWeNyqnIjH58JKi8Y37T2hOPeAWaK7uL3NFr4eJ+kATKYfCC/v9VXWeKuQ==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.46.0", - "@wordpress/block-editor": "^12.17.0", - "@wordpress/blocks": "^12.26.0", - "@wordpress/compose": "^6.26.0", - "@wordpress/data": "^9.19.0", - "@wordpress/deprecated": "^3.49.0", - "@wordpress/element": "^5.26.0", - "@wordpress/html-entities": "^3.49.0", - "@wordpress/i18n": "^4.49.0", - "@wordpress/is-shallow-equal": "^4.49.0", - "@wordpress/private-apis": "^0.31.0", - "@wordpress/rich-text": "^6.26.0", - "@wordpress/sync": "^0.11.0", - "@wordpress/undo-manager": "^0.9.0", - "@wordpress/url": "^3.50.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/sync": "^0.15.0", + "@wordpress/undo-manager": "^0.13.0", + "@wordpress/url": "^3.54.0", "change-case": "^4.1.2", "equivalent-key-map": "^0.2.2", "fast-deep-equal": "^3.1.3", @@ -33602,15 +33361,6 @@ "uuid": "^9.0.1" }, "dependencies": { - "@wordpress/private-apis": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.31.0.tgz", - "integrity": "sha512-Hx2LJfkgbeAixXHDvi/rBly4+mShhrJfYXwyh6uTLnXkjp6OcPuBbCXhIfARw45lNdiqWdHoqXcAl1RTBFFd4g==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0" - } - }, "uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -33620,19 +33370,19 @@ } }, "@wordpress/data": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.20.0.tgz", - "integrity": "sha512-3cm2te6NUj/X1zzmRO+WhueCanjocniX6sJFVzkg5mGXme6wFI8iSOnGPKlMkGcZGd0fVei1ydBKaIUMjrPBTQ==", + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.23.0.tgz", + "integrity": "sha512-CtVgMpP5CG2upzAQfB43cUDo8K24KIU/54FPZzZwM4PnOvpIZ5orhLQP3Gj/cmtr/U54d//DV52vCtuGD+qugg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/compose": "^6.27.0", - "@wordpress/deprecated": "^3.50.0", - "@wordpress/element": "^5.27.0", - "@wordpress/is-shallow-equal": "^4.50.0", - "@wordpress/priority-queue": "^2.50.0", - "@wordpress/private-apis": "^0.32.0", - "@wordpress/redux-routine": "^4.50.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/is-shallow-equal": "^4.53.0", + "@wordpress/priority-queue": "^2.53.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/redux-routine": "^4.53.0", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", "is-plain-object": "^5.0.0", @@ -33640,93 +33390,81 @@ "redux": "^4.1.2", "rememo": "^4.0.2", "use-memo-one": "^1.1.1" - }, - "dependencies": { - "@wordpress/private-apis": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.32.0.tgz", - "integrity": "sha512-P7nxI/bGMDQhtlTfSe1Y2SDfrd20K5UMnTHbq+hmIkzBGRpNPbdGeNu2bZaZtIvmXk1OCR0Fkef+e6QqkOfYPg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0" - } - } } }, "@wordpress/date": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-4.50.0.tgz", - "integrity": "sha512-FhfaG6YRXWmni66RjwhCB7rQNlLJ05+qTa/jXrj2UNWDNv/sfZ6Ky+b/rKrrUnLaIs9pGiW1195cSxsAS4EY3w==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-4.53.0.tgz", + "integrity": "sha512-Jfs7zYELB84Reo5DJ3pjr609IyWE3oJwubh5oZQI8E28hwj1OkGn6tY6qchg4QGnDyUcb+S9JDaJttG0wJGEfw==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/deprecated": "^3.50.0", + "@wordpress/deprecated": "^3.53.0", "moment": "^2.29.4", "moment-timezone": "^0.5.40" } }, "@wordpress/dependency-extraction-webpack-plugin": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-4.31.0.tgz", - "integrity": "sha512-Xpm8EEhi6e8GL1juYh/70AFbcE/ZVXJ3p47KMkkEsn5t+hG9QHjKe2lTj98v2r3rB+ampoK+whdV1w6gItXYpw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-6r1Nsq/yoJSqx35iqXeqjID8GGrN7mISZXWCjbuYLIRRY1FxU+wbenj2BsdUtyUuAv8tAUH79cpgO0poYEDMoQ==", "dev": true, "requires": { - "json2php": "^0.0.7", - "webpack-sources": "^3.2.2" + "json2php": "^0.0.7" } }, "@wordpress/deprecated": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-3.50.0.tgz", - "integrity": "sha512-DL01l0Wlo3df9OcSGHP11Ot/nq0HytbdmD+iPkiCCRI6Xctepbs/DzRR2CO3qLrJkWn6RReFwZWZZjzI7lZUqg==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-3.53.0.tgz", + "integrity": "sha512-o6oXvOaEHYDbShDqmthVijiXo0cWdHpPbL6PFZQuqHWvDa9FyxLkfYym2/REZJIFaMJsf6BGZEJMrXTf/W+Nrg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.50.0" + "@wordpress/hooks": "^3.53.0" } }, "@wordpress/dom": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-3.50.0.tgz", - "integrity": "sha512-rMnV1ysGOHbKnmjLQYwGkT1co1iEkC3YsKrEObP8mklw1R7rbCy7fc2brIz7kqcHU1DRyg/+7wOCMkg8a/EV/Q==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-3.53.0.tgz", + "integrity": "sha512-mhRAqhDpgfo4cgtd9PGoVvQ7cJQbiMNOgL3BsvRWXDQO2De3cYM8gnJWoNbRW6UOdtMDSAKBM58iJ2PnmIPjbg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/deprecated": "^3.50.0" + "@wordpress/deprecated": "^3.53.0" } }, "@wordpress/dom-ready": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.50.0.tgz", - "integrity": "sha512-97tJpat1emXnwfGlJMiG6p37CpHJXDLmM/SIbsGJ0Oj8P4/TXbTuE9DNT1H8B1wKe5zD7kICjp48y91ugmgSrQ==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.53.0.tgz", + "integrity": "sha512-0sKnHwWoSxFrazehbxg4gZwTMCe1qIC4u2jUjVDviTlUMn3vsx5GdXNi0a7nYdR3Oiq7/a5JRKWsgXtIpeprnw==", "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/e2e-test-utils": { - "version": "10.21.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-10.21.0.tgz", - "integrity": "sha512-Oh62GkqAKBIyD0IO3/Oa0l42yL/jbpTRDyh8H+t6gZbHWYTDvEGEr/LOqI9bk5Lwk7Jt5jpN6136FDwyMzHSXw==", + "version": "10.24.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-10.24.0.tgz", + "integrity": "sha512-y7//z4Fw85zvhD6nD3x8z2C0EhcQOaLQxRc2O0XBsM8nsOGE2NDJzTa7xwPtAOFP8StnytfgAK7xCSkM0UGYFg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.47.0", - "@wordpress/keycodes": "^3.50.0", - "@wordpress/url": "^3.51.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/url": "^3.54.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "node-fetch": "^2.6.0" } }, "@wordpress/e2e-test-utils-playwright": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.16.0.tgz", - "integrity": "sha512-CktRj5/Cc/pAvTHXIAPIMrmmnb0VjtXbTGSjYG6pW/JI2YAmpwY2yBA+DlHJjqOIpcjDDj+sSsJomRSxT2chwQ==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.21.0.tgz", + "integrity": "sha512-/tnTaTcjJ4LzmZtsdZCTO77qmj3p2m4+Nmvf2v0iB3/AgRIKazndecfZ9YnY2guos5Kl8QBjVkdzQ9Xc28LOpw==", "dev": true, "requires": { - "@wordpress/api-fetch": "^6.45.0", - "@wordpress/keycodes": "^3.48.0", - "@wordpress/url": "^3.49.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/url": "^3.54.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "get-port": "^5.1.1", @@ -33744,132 +33482,102 @@ } }, "@wordpress/edit-post": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-7.24.0.tgz", - "integrity": "sha512-lfbD+3KGjSoCwzG7WtfyoWkSlyGiE+RWNUzehpN5m/Y0mc/YNA5j4EkNNwE8Qr0Iz4GypoLw7lXJBPNCqOP7AQ==", + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-7.30.0.tgz", + "integrity": "sha512-vnQjrLaG830Ysx05CHkLcqtHs+Dtj8oQBezGS4Jhg9S5e14Y0HZCIeFJ3GARgSDZoCTeX87WSyEDf7XlpZKxJA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.47.0", - "@wordpress/api-fetch": "^6.44.0", - "@wordpress/block-editor": "^12.15.0", - "@wordpress/block-library": "^8.24.0", - "@wordpress/blocks": "^12.24.0", - "@wordpress/commands": "^0.18.0", - "@wordpress/components": "^25.13.0", - "@wordpress/compose": "^6.24.0", - "@wordpress/core-commands": "^0.16.0", - "@wordpress/core-data": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/deprecated": "^3.47.0", - "@wordpress/dom": "^3.47.0", - "@wordpress/editor": "^13.24.0", - "@wordpress/element": "^5.24.0", - "@wordpress/hooks": "^3.47.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/interface": "^5.24.0", - "@wordpress/keyboard-shortcuts": "^4.24.0", - "@wordpress/keycodes": "^3.47.0", - "@wordpress/media-utils": "^4.38.0", - "@wordpress/notices": "^4.15.0", - "@wordpress/plugins": "^6.15.0", - "@wordpress/preferences": "^3.24.0", - "@wordpress/private-apis": "^0.29.0", - "@wordpress/url": "^3.48.0", - "@wordpress/viewport": "^5.24.0", - "@wordpress/warning": "^2.47.0", - "@wordpress/widgets": "^3.24.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/block-library": "^8.30.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/commands": "^0.24.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-commands": "^0.22.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/editor": "^13.30.0", + "@wordpress/element": "^5.30.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/interface": "^5.30.0", + "@wordpress/keyboard-shortcuts": "^4.30.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/media-utils": "^4.44.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/plugins": "^6.21.0", + "@wordpress/preferences": "^3.30.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/url": "^3.54.0", + "@wordpress/viewport": "^5.30.0", + "@wordpress/warning": "^2.53.0", + "@wordpress/widgets": "^3.30.0", "classnames": "^2.3.1", "memize": "^2.1.0", "rememo": "^4.0.2" } }, "@wordpress/editor": { - "version": "13.25.0", - "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-13.25.0.tgz", - "integrity": "sha512-1waT0ZG6ii98eOuqbOJMEE9vHp1Y8BGlnb37rwiFn61yeUnWuvH2qcHyPiRh4tUDYp3rHACa+P0joN4L32MeHA==", + "version": "13.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-13.30.0.tgz", + "integrity": "sha512-++tYN1rCLUCjVZm8/qknvmMZXTbM/M5EQcVy0qqlwdryMfR6tQcbze2lkJeN6dbWlrOwmJ+o+J/qwwxgUMiMQg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.48.0", - "@wordpress/api-fetch": "^6.45.0", - "@wordpress/blob": "^3.48.0", - "@wordpress/block-editor": "^12.16.0", - "@wordpress/blocks": "^12.25.0", - "@wordpress/commands": "^0.19.0", - "@wordpress/components": "^25.14.0", - "@wordpress/compose": "^6.25.0", - "@wordpress/core-data": "^6.25.0", - "@wordpress/data": "^9.18.0", - "@wordpress/date": "^4.48.0", - "@wordpress/deprecated": "^3.48.0", - "@wordpress/dom": "^3.48.0", - "@wordpress/element": "^5.25.0", - "@wordpress/hooks": "^3.48.0", - "@wordpress/html-entities": "^3.48.0", - "@wordpress/i18n": "^4.48.0", - "@wordpress/icons": "^9.39.0", - "@wordpress/keyboard-shortcuts": "^4.25.0", - "@wordpress/keycodes": "^3.48.0", - "@wordpress/media-utils": "^4.39.0", - "@wordpress/notices": "^4.16.0", - "@wordpress/patterns": "^1.9.0", - "@wordpress/preferences": "^3.25.0", - "@wordpress/private-apis": "^0.30.0", - "@wordpress/reusable-blocks": "^4.25.0", - "@wordpress/rich-text": "^6.25.0", - "@wordpress/server-side-render": "^4.25.0", - "@wordpress/url": "^3.49.0", - "@wordpress/wordcount": "^3.48.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/blob": "^3.53.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/commands": "^0.24.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/date": "^4.53.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/dom": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/keyboard-shortcuts": "^4.30.0", + "@wordpress/keycodes": "^3.53.0", + "@wordpress/media-utils": "^4.44.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/patterns": "^1.14.0", + "@wordpress/preferences": "^3.30.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/reusable-blocks": "^4.30.0", + "@wordpress/rich-text": "^6.30.0", + "@wordpress/server-side-render": "^4.30.0", + "@wordpress/url": "^3.54.0", + "@wordpress/wordcount": "^3.53.0", "classnames": "^2.3.1", "date-fns": "^2.28.0", "memize": "^2.1.0", "react-autosize-textarea": "^7.1.0", "rememo": "^4.0.2", "remove-accents": "^0.5.0" - }, - "dependencies": { - "@wordpress/commands": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-0.19.0.tgz", - "integrity": "sha512-HGI59spV/efsbBaZ1/MkFFIjCSiQRd3u+oEAyRY8KWhxZBkEbJPnxBVb1I+LZ4GkhMNwRLAeo2VrI1HoYbs6eQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.14.0", - "@wordpress/data": "^9.18.0", - "@wordpress/element": "^5.25.0", - "@wordpress/i18n": "^4.48.0", - "@wordpress/icons": "^9.39.0", - "@wordpress/keyboard-shortcuts": "^4.25.0", - "@wordpress/private-apis": "^0.30.0", - "classnames": "^2.3.1", - "cmdk": "^0.2.0", - "rememo": "^4.0.2" - } - }, - "@wordpress/private-apis": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.30.0.tgz", - "integrity": "sha512-mkz2QtbSVNAsFNXBni5XMLV1KYhQAx1vyC5KcEyeQADiRkRUW6XJ+u53WwQfpdjvsEQhkyGpK13Rl7gt3KOpeQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0" - } - } } }, "@wordpress/element": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.27.0.tgz", - "integrity": "sha512-IA5LTAfx5bDNXULPmctcNb/04i4JcnIReG0RAuPgrZ8lbMZWUxGFymh10PEQjs7ZJ++qGsI6E+6JISpjkRaDQQ==", + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.30.0.tgz", + "integrity": "sha512-KH+KdZ1jzLRgA65Ez6Uy5dVbkS2az0uk1lDUpPRhApEY2J12SbsD/aVuznP/huO2Af+hyh4DDqbVS817Abcy2g==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", - "@wordpress/escape-html": "^2.50.0", + "@wordpress/escape-html": "^2.53.0", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.2.0", @@ -33877,9 +33585,9 @@ } }, "@wordpress/env": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-9.2.0.tgz", - "integrity": "sha512-2gl65WYbkuTjnW2SHKjeqdpLTgnPc/xVvFiwG+2p/RJwDHSuw1xXSdFqFUh3+wC/4cuXy9b2ZBm/SYsBoc8DDw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-9.5.0.tgz", + "integrity": "sha512-dxIosImyvkqUqPgCcOUCTgnLQRG3dZhnnNomEnsY0z5stbN+2IS6EJq3mZ/oJDitcJjrqB0dPopdUTVS/9bWMA==", "dev": true, "requires": { "chalk": "^4.0.0", @@ -33948,25 +33656,25 @@ } }, "@wordpress/escape-html": { - "version": "2.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.50.0.tgz", - "integrity": "sha512-hBvoMCEZocziZDGCmBanSO+uupnd054mxd7FQ6toQ4UnsZ4JwXSmEC72W2Ed+cRGB1DeJDD0dY9iC0b4xkumsQ==", + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.53.0.tgz", + "integrity": "sha512-K2c6jg7qTGZIFj7uTCFR4FTK8PqHM4El7zdPAuK2apnZqhbdJEfH1/ogK+QZtn1VctyOXl0Mc+vzWoLUncey3g==", "dev": true, "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/eslint-plugin": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-17.7.0.tgz", - "integrity": "sha512-JSFaCogE0WlZpl0SV4q8DK8G6jwDjEzXRzOsgesWilea4OuVp1KxCamkddTorRNM3QAbjrGuPJ4NYaGrNG9QsA==", + "version": "17.10.0", + "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-17.10.0.tgz", + "integrity": "sha512-fMmMzBMR8z7p2yYTMtEEnzoYmdFdv0HdrM2b7s9693fYxtYQv/FaxUKdep6slMiVt/DBoPUmuDGgZsttzOTwng==", "dev": true, "requires": { "@babel/eslint-parser": "^7.16.0", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "^7.34.0", - "@wordpress/prettier-config": "^3.7.0", + "@wordpress/babel-preset-default": "^7.37.0", + "@wordpress/prettier-config": "^3.10.0", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", @@ -33999,31 +33707,31 @@ } }, "@wordpress/hooks": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.50.0.tgz", - "integrity": "sha512-YIhwT1y0ss7Byfz46NBx08EUmXzWMu+g5DCY7FMuDNhwxSEoZMB8edKMiwNmFk4mFKBCnXM1d5FeONUPIUkJwg==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.53.0.tgz", + "integrity": "sha512-Ul8iS+bzJMuN/ZD1HTcs3fwXC6eKLCvJJZmb61tEQ+Z3dubjf6vFQizE6Tl3ZaVlcc2/rtwb0rdEPLfnR6ePFg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/html-entities": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-3.50.0.tgz", - "integrity": "sha512-DBRgShv6FLtDpapoTgmEx//6uHeq+mk5zKhAWAAqu6+/6LqOm/TCoUTxb0E2xtHh4oRBgU5nYC92pObRaczFdQ==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-3.53.0.tgz", + "integrity": "sha512-fqO6oJnBs1hLNtRLQLIYkKcnISpwf/ts4hOcBr+SN1pusTM3Om9fSUYjjRwXzjnqQsm/Tqjnh3GyDNzmmyuz5w==", "dev": true, "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/i18n": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.50.0.tgz", - "integrity": "sha512-FkA2se6HMQm4eFC+/kTWvWQqs51VxpZuvY2MlWUp/L1r1d/dMBHXu049x86+/+6yk3ZNqiK5h6j6Z76dvPHZ4w==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.53.0.tgz", + "integrity": "sha512-Ovp00blCGhgKwtLQbxT/gweB9r9vGPLkneM28KhCQvgHIlpyDRESIr+CMIDov7KxKnO2gzfnwfrNhXgZX+a/3Q==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.50.0", + "@wordpress/hooks": "^3.53.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "sprintf-js": "^1.1.1", @@ -34031,61 +33739,71 @@ } }, "@wordpress/icons": { - "version": "9.41.0", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.41.0.tgz", - "integrity": "sha512-L4fp9ZdxGBpMk3o2YqABgiPHNoHyu9Enid7JNkCdWP8iUgk7dEiDvo/XoiWPTAeNbF6W8Nqu54635mq01es0NQ==", + "version": "9.44.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.44.0.tgz", + "integrity": "sha512-8VxsFkcUYGCk6qfrriSOxOFSDSgl4tNRnJo7019ABj8mTr4pqdJC7tYz88rOvtBbx9tswWXLBJM49vaBG8mOpw==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.27.0", - "@wordpress/primitives": "^3.48.0" + "@wordpress/element": "^5.30.0", + "@wordpress/primitives": "^3.51.0" } }, "@wordpress/interactivity": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-3.0.0.tgz", - "integrity": "sha512-/UqtStwrUWY5/HsXe7i/0p3c77+mEmqDOFxqK2VdMdl5FF2PeFqqVhAJrsBE40Sbtia2Im+9yH/rfyo4zNtBnw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-5.2.0.tgz", + "integrity": "sha512-0TmHT2LYpkONpP+7KwZrdnG0T1eoWsRG3DYmei1i05aWbThSTBxRsZCgDrURgLMPOvLvjumBtzXLHZoUSKvV+g==", + "dev": true, + "requires": { + "@preact/signals": "^1.2.2", + "deepsignal": "^1.4.0", + "preact": "^10.19.3" + } + }, + "@wordpress/interactivity-router": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity-router/-/interactivity-router-1.3.0.tgz", + "integrity": "sha512-NRrfBwGzTTeKMmQHIWp4yCAmBNGzHcSc2ot6WNbFmX3WtGkIvYHG6pKhSfl2Hx9ZpJN7og7II6J/VjMGSfelRQ==", "dev": true, "requires": { - "@preact/signals": "^1.1.3", - "deepsignal": "^1.3.6", - "preact": "^10.13.2" + "@wordpress/interactivity": "^5.2.0" } }, "@wordpress/interface": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-5.24.0.tgz", - "integrity": "sha512-zRzYG4QM5nPJ2FAuLRxyW7RW1F+Pz3qpPpSBUvQusjnbc8r9/gLFtXXIALHukOF6l6uQ67a1rm4752aFoCWj0A==", + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-5.30.0.tgz", + "integrity": "sha512-HIHQKI/tkIuJzM8unZeYsOPgaEPo4gZ81BmopKR5NRK4qNlhK42u9gpW4eUOwgxqm+Ngw5QFLiEX4vnOU1C8+A==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.47.0", - "@wordpress/components": "^25.13.0", - "@wordpress/compose": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/deprecated": "^3.47.0", - "@wordpress/element": "^5.24.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/plugins": "^6.15.0", - "@wordpress/preferences": "^3.24.0", - "@wordpress/viewport": "^5.24.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/plugins": "^6.21.0", + "@wordpress/preferences": "^3.30.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/viewport": "^5.30.0", "classnames": "^2.3.1" } }, "@wordpress/is-shallow-equal": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.50.0.tgz", - "integrity": "sha512-lX0fMa1f/TwWYYF+Oj0MG2Eze4Bb+vsnhXX6X1l+Ri3PG34wWGonjq729qHbJRDwm8o1y9GeswCgESIpuAm9wg==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.53.0.tgz", + "integrity": "sha512-/90Nd1u90yTQxtwv6c+TnGyBFvxrF04pfo1KhdodCHlX/+sBn4CASgSvQAW2+FXemNqfw0rC4mBG/Sa8xhtC5A==", "dev": true, "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/jest-console": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.19.0.tgz", - "integrity": "sha512-x35izGNCLo7xoK770I7O/+m6sE/a9lmo6QqyDoR1AZaUwk0PAY35EGrbbi3FfXeReTXBRNJ1MpnQyvskg8o/Gw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.24.0.tgz", + "integrity": "sha512-mCcoEGrFDN78QMWSXVH3B5RVdwNNzsODA8g6LSbmxKY5T/4xuOgF/Kj6mS0YSrCxyOiDUTW9VgHoQtx2MwS+Qg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", @@ -34093,168 +33811,159 @@ } }, "@wordpress/jest-preset-default": { - "version": "11.19.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.19.0.tgz", - "integrity": "sha512-9BbUDZaa6Cg9dz+JyfOe30C8JJrhCkyaFqCqSNJEcyB4KK83qp2QRkblVXABmHarw4oPfg+OJLLALIAA045a0w==", + "version": "11.24.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.24.0.tgz", + "integrity": "sha512-W3y5PYdOwzm1oKdx+8XZoLeXEV4FI/LnYJpcnKxIyQxfPm4cI8WlRMUT6DAyW6nKv67a0FUwoA7XJ9kXH1pASA==", "dev": true, "requires": { - "@wordpress/jest-console": "^7.19.0", + "@wordpress/jest-console": "^7.24.0", "babel-jest": "^29.6.2" } }, "@wordpress/keyboard-shortcuts": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-4.26.0.tgz", - "integrity": "sha512-ijCDTSKmWUP4sanucgrOqhSaxqBE1nbR2FzBEITSSfh2x1i0IK5rzF5BL3waV4mWKuSe0UmpPz5vnqKvijc+Ug==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-4.30.0.tgz", + "integrity": "sha512-ICEFcw6p/uuXMddnDqMglR74p/uAUX1Rr4RM1BoZ7BqmopPTey4hQz8bT/FfogBpjl0QulD7D6rh19aiZ/ppfQ==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/data": "^9.19.0", - "@wordpress/element": "^5.26.0", - "@wordpress/keycodes": "^3.49.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/keycodes": "^3.53.0", "rememo": "^4.0.2" } }, "@wordpress/keycodes": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.50.0.tgz", - "integrity": "sha512-ykWpyCbgwcaT8i5kSfotYtd2oOHyMDpWEYR73InYrzEhl7pnS3wD7hi/KfeKLvMfYhbysUXlCVr6q/oH+qK/DQ==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.53.0.tgz", + "integrity": "sha512-fNmjwZCdKg0EqXUQTJzdLj4sC58vIp8UqRKg+DeHQX4xAjLyTN8/JzMvmxPFF2nv57a1J5FLtOONqdrxaETGYg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.50.0" + "@wordpress/i18n": "^4.53.0" } }, "@wordpress/media-utils": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@wordpress/media-utils/-/media-utils-4.39.0.tgz", - "integrity": "sha512-vJRqqnEIsAvgy82daf+qE87ncB4o8x2Y/1NbvqUJUvU/B+xDxRxZeOYdwcYIhb7VNFkWo+DMxtIuhiNX67XiTQ==", + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@wordpress/media-utils/-/media-utils-4.44.0.tgz", + "integrity": "sha512-o6QqamGg3CwwFxwZW+lwmsihUr5csY+3wpeLrvC2xxJEGo6Vr/GVnFGtBmFRhRaa91aT4CoX/xlzTXpRRheoCg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.45.0", - "@wordpress/blob": "^3.48.0", - "@wordpress/element": "^5.25.0", - "@wordpress/i18n": "^4.48.0" + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/blob": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0" } }, "@wordpress/notices": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-4.17.0.tgz", - "integrity": "sha512-EH7f4YDQUtuY+UlS8OIv0bjXXK+SGMGPQNlecSKFoP3QBoXZy5zhVDAfr4vewPE19t3gWaf22zPtF0NTl06a2g==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-4.21.0.tgz", + "integrity": "sha512-clyPRDhVbG7g1n1JDLLOimfBi5e6b9EekZv/P9amxCQxAvFquwDoAvQtUbuz6unF8sFLRtvLO5LnNqGwEnL/eg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.49.0", - "@wordpress/data": "^9.19.0" + "@wordpress/a11y": "^3.53.0", + "@wordpress/data": "^9.23.0" } }, "@wordpress/npm-package-json-lint-config": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.33.0.tgz", - "integrity": "sha512-GBVGcn6xAqrWQueSlMVMHoebGsHvildWwcJ/lIpxh7i7V/VBoc9Z8rdUEKAip6lTjZx+mCmzXQH4hU3QdNA/RA==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.38.0.tgz", + "integrity": "sha512-RT5dDPaHiPNzVTcLV6HTHArRBPzAhQPMKZi2BX16rrlYLwcr9TNMVJQ787RXxLmkr83BvudZqsHF4va06FwVLQ==", "dev": true, "requires": {} }, "@wordpress/patterns": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-1.9.0.tgz", - "integrity": "sha512-acuP9k41lGQD/jMcAbiChx5XRP5HUfyre1hJeL+eqAC/SYF/BnDKrhxugoXnOIpJDxrlOZ968csZukbj088QnQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-1.14.0.tgz", + "integrity": "sha512-QRuakdYJCPpnY8sZQzad2Gyr+oWk6/VSMpTDbLsZ4b44XIBk6eDtYloUNaoRMzbjQDaee69GV24xXZnzzDdUuA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.48.0", - "@wordpress/block-editor": "^12.16.0", - "@wordpress/blocks": "^12.25.0", - "@wordpress/components": "^25.14.0", - "@wordpress/compose": "^6.25.0", - "@wordpress/core-data": "^6.25.0", - "@wordpress/data": "^9.18.0", - "@wordpress/element": "^5.25.0", - "@wordpress/html-entities": "^3.48.0", - "@wordpress/i18n": "^4.48.0", - "@wordpress/icons": "^9.39.0", - "@wordpress/notices": "^4.16.0", - "@wordpress/private-apis": "^0.30.0", - "@wordpress/url": "^3.49.0", - "nanoid": "^3.3.4" - }, - "dependencies": { - "@wordpress/private-apis": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.30.0.tgz", - "integrity": "sha512-mkz2QtbSVNAsFNXBni5XMLV1KYhQAx1vyC5KcEyeQADiRkRUW6XJ+u53WwQfpdjvsEQhkyGpK13Rl7gt3KOpeQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0" - } - } + "@wordpress/a11y": "^3.53.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/html-entities": "^3.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/url": "^3.54.0" } }, "@wordpress/plugins": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-6.18.0.tgz", - "integrity": "sha512-m2BRJ5BApIMwT2Ck5E5yD8pS3RiIoOvWhzsYWrRqRfwjRhc6K46BreCbkiHgduBaFgzDIWpujlUHkYtdl27RoQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-6.21.0.tgz", + "integrity": "sha512-q3AVdhIk7/mTrPMY3llEQJvupbsN+1L6m2YF3AYnPB9VVRTRb4RDlor28YWxPwlliRSdTy2Q5t5orWtNDD0s4w==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^25.16.0", - "@wordpress/compose": "^6.27.0", - "@wordpress/element": "^5.27.0", - "@wordpress/hooks": "^3.50.0", - "@wordpress/icons": "^9.41.0", - "@wordpress/is-shallow-equal": "^4.50.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/element": "^5.30.0", + "@wordpress/hooks": "^3.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/is-shallow-equal": "^4.53.0", "memize": "^2.0.1" } }, "@wordpress/postcss-plugins-preset": { - "version": "4.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.32.0.tgz", - "integrity": "sha512-+4+chYW8pRd7Irzm8lXom5Axs765q4me1mT+FBskfotUroAvoJtmfAybmyhIvTirTwLaN7ugOYiSBjAD6M7+rg==", + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.37.0.tgz", + "integrity": "sha512-9k0OFThyn73hmZ0NRWtrYDEHk8BHwzMLrovqtt9fsBQRQviz4kjLOFkGvSQmvFPbnaMK1ZG5WOhV8/RkKK8tig==", "dev": true, "requires": { - "@wordpress/base-styles": "^4.39.0", + "@wordpress/base-styles": "^4.44.0", "autoprefixer": "^10.2.5" } }, "@wordpress/preferences": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-3.26.0.tgz", - "integrity": "sha512-8fXN9T1sh9g6kl3ta0BWlZKeqlvMGj2VhNd564zZdfOsEojW1Fhq2RoLahcp2BnMmSojdgPCSQQ8O2IdirwDyA==", + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-3.30.0.tgz", + "integrity": "sha512-8GfcEWerwliMTs/hpKbYHxF0SnH/ghbpyUHk13hdZsJwIYFN/DGS9KPbeQmoMdJIOS5YUxhQ1dXCxJIjBmpSlA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.49.0", - "@wordpress/components": "^25.15.0", - "@wordpress/data": "^9.19.0", - "@wordpress/element": "^5.26.0", - "@wordpress/i18n": "^4.49.0", - "@wordpress/icons": "^9.40.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/private-apis": "^0.35.0", "classnames": "^2.3.1" } }, "@wordpress/prettier-config": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-3.7.0.tgz", - "integrity": "sha512-JRTc5p7UxtcPkqdSDXSFJoJnVuS510uiRVz8anXEl5nuOx5p+SJAzi9QPrxTgOE8bN3wRABH4eIhfOcta4CFdg==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-3.10.0.tgz", + "integrity": "sha512-0zA3K1zDyRjUhTY+zKfBvQMKqEbYK/hC3NOabEWZ++pvT5JYJrD7ZVXE+l5TDVd/d2rqxM0eLssh/yIyWyaeSQ==", "dev": true, "requires": {} }, "@wordpress/primitives": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.48.0.tgz", - "integrity": "sha512-uBoMxpl+FiZF6aRXH/+Hwol4EAL6QqlNSaGF1IzEwklFzdRF1m5wTM4vh21w8Bq7lgxiuAqyueY7X5u32v+zPw==", + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.51.0.tgz", + "integrity": "sha512-UKz0h3BIU4hDMXnNlLZ6CZCe7eWuhDhzLj25+Ldfb71RuXMoqdH31ZN3gwIljVrLRWcBKVtgp9ULmuhdGvwzDA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.27.0", + "@wordpress/element": "^5.30.0", "classnames": "^2.3.1" } }, "@wordpress/priority-queue": { - "version": "2.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.50.0.tgz", - "integrity": "sha512-21E842EVFYUd1ZrNTLAW57IyloDCUZr6h1Te6BgqKoeKOEteoTQwA9BemMzZJUiThUSZymW94ot0Omb+C8VX2g==", + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.53.0.tgz", + "integrity": "sha512-tWsEOzstjE05DGyP9lGtwD6Ver6O1emKFQCHVXqS5kPfTg44Tm4/JTaCpnYYAf/+ki9ueZgTczqIdukCu+PDFA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", @@ -34262,18 +33971,18 @@ } }, "@wordpress/private-apis": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.29.0.tgz", - "integrity": "sha512-8t4au9+IXXgJlxxOuYVYi8PKp0uWajNYILNfqCLB65jQEClzGNMQhU6MeJ9+kHN3gdOltMk7UzG28X+FTDlmkQ==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.35.0.tgz", + "integrity": "sha512-ta+k1VfwFFj3+JjpANwhancgEZEznYOvdVcKeLAlhKbM10IwIX2jGqwTjHsoN+C4o/8eoLi4RgJgdDWHGXiGrw==", "dev": true, "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/redux-routine": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-4.50.0.tgz", - "integrity": "sha512-giHjQYhmFDCpeNEnsZKP0JNPBnpuQwsoxLmHAUUSNFWAmd4rtnNnG6M8HuqOLmgYTvEa8Hlx3Bl+reTGvrtI2g==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-4.53.0.tgz", + "integrity": "sha512-95RZKRjCf++y7Lur2pGWP+e70buJ94+gyASTewlMfxeK3l6QWempghLEJNMrOSyx+FckFJnsD8qQyXVCDA7Cwg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", @@ -34283,87 +33992,76 @@ } }, "@wordpress/reusable-blocks": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-4.25.0.tgz", - "integrity": "sha512-R7ysUJMRcxfGeF0Ly7/5QK7L32aHsGHXamSQY5h+aBhdNhKRtElWZKvzaKiw7AmKUqWpCQtpa8elFm2Iim//vw==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-4.30.0.tgz", + "integrity": "sha512-1tgjuoTgWqMSFFEXpP1od1TxMDdlEw4MTvqrl9ItZxERdEBggzRFhwhbyW2LpWe9C+g0svqVEUwv0nVjEUV9Zg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^12.16.0", - "@wordpress/blocks": "^12.25.0", - "@wordpress/components": "^25.14.0", - "@wordpress/core-data": "^6.25.0", - "@wordpress/data": "^9.18.0", - "@wordpress/element": "^5.25.0", - "@wordpress/i18n": "^4.48.0", - "@wordpress/icons": "^9.39.0", - "@wordpress/notices": "^4.16.0", - "@wordpress/private-apis": "^0.30.0", - "@wordpress/url": "^3.49.0" - }, - "dependencies": { - "@wordpress/private-apis": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-0.30.0.tgz", - "integrity": "sha512-mkz2QtbSVNAsFNXBni5XMLV1KYhQAx1vyC5KcEyeQADiRkRUW6XJ+u53WwQfpdjvsEQhkyGpK13Rl7gt3KOpeQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.16.0" - } - } + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/components": "^27.1.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/notices": "^4.21.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/url": "^3.54.0" } }, "@wordpress/rich-text": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.27.0.tgz", - "integrity": "sha512-B7t++SldcI4nb+lO2m9oEdyD8y2FbH5DKY5F2G3xpcEnw4EKSt4SsTzeclMQ/2zzlEHPRKU/IR29SeOIJ1H8JQ==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.30.0.tgz", + "integrity": "sha512-zVc7pRzPajP5M1g79EvLMsvBkduu19TdwbxZrwzHD7xY+jfPdRAzNV8UMw41lVrH203yrAN1tfcJw4Hshjl/VQ==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/a11y": "^3.50.0", - "@wordpress/compose": "^6.27.0", - "@wordpress/data": "^9.20.0", - "@wordpress/deprecated": "^3.50.0", - "@wordpress/element": "^5.27.0", - "@wordpress/escape-html": "^2.50.0", - "@wordpress/i18n": "^4.50.0", - "@wordpress/keycodes": "^3.50.0", + "@wordpress/a11y": "^3.53.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/escape-html": "^2.53.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/keycodes": "^3.53.0", "memize": "^2.1.0", "rememo": "^4.0.2" } }, "@wordpress/router": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-0.16.0.tgz", - "integrity": "sha512-IWxELFlzLokckJbP6/NOcojR1OTGx81flQwI2OGZe88Zzjl9yqKXtoKQEA1WA10WKCGNZIncI9QnaCD/mgg8jg==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-0.22.0.tgz", + "integrity": "sha512-f7QQEQ+C2wRDmI7crfkhtrjcKTIS4pCBPEoUwaNYJ4v8Dnc72OvFr5qHwNjG8mHZRoq+VANkcaY8hNsZ3ehN+A==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.24.0", - "@wordpress/private-apis": "^0.29.0", - "@wordpress/url": "^3.48.0", + "@wordpress/element": "^5.30.0", + "@wordpress/private-apis": "^0.35.0", + "@wordpress/url": "^3.54.0", "history": "^5.1.0" } }, "@wordpress/scripts": { - "version": "26.19.0", - "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-26.19.0.tgz", - "integrity": "sha512-m3QYlgpWRfIqCfU4jWKwGeA12Qkt6d9CMewEIxIBGVlEGd/sL5rU1fM7LKNBEbSPQpaOTWJApNGWPcW75Fwp+w==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-27.4.0.tgz", + "integrity": "sha512-DAX1n/nWtOH77jeHxUFrDiqXGc5OVsDeynyvJOxMMMdi1otN/iO6MkFOg0ExzRXgV4/+8DVpN1RWgeuXLzrBGw==", "dev": true, "requires": { "@babel/core": "^7.16.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "^7.32.0", - "@wordpress/browserslist-config": "^5.31.0", - "@wordpress/dependency-extraction-webpack-plugin": "^4.31.0", - "@wordpress/e2e-test-utils-playwright": "^0.16.0", - "@wordpress/eslint-plugin": "^17.5.0", - "@wordpress/jest-preset-default": "^11.19.0", - "@wordpress/npm-package-json-lint-config": "^4.33.0", - "@wordpress/postcss-plugins-preset": "^4.32.0", - "@wordpress/prettier-config": "^3.5.0", - "@wordpress/stylelint-config": "^21.31.0", + "@wordpress/babel-preset-default": "^7.37.0", + "@wordpress/browserslist-config": "^5.36.0", + "@wordpress/dependency-extraction-webpack-plugin": "^5.4.0", + "@wordpress/e2e-test-utils-playwright": "^0.21.0", + "@wordpress/eslint-plugin": "^17.10.0", + "@wordpress/jest-preset-default": "^11.24.0", + "@wordpress/npm-package-json-lint-config": "^4.38.0", + "@wordpress/postcss-plugins-preset": "^4.37.0", + "@wordpress/prettier-config": "^3.10.0", + "@wordpress/stylelint-config": "^21.36.0", "adm-zip": "^0.5.9", "babel-jest": "^29.6.2", "babel-loader": "^8.2.3", @@ -34627,28 +34325,28 @@ } }, "@wordpress/server-side-render": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-4.25.0.tgz", - "integrity": "sha512-ScR5axX8TI6B0YDyZoZJReKSF0muxkx93Qb0buPBv9UxZ/Yv+U3GtNOFZvDZcn6hmHB99qkhuCS0UEVg56isqQ==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-4.30.0.tgz", + "integrity": "sha512-JNT3v2zvdghs7jtNhtQlj0miv+dmwKqyCcZAZ6KLwQEXeU959FmfizV2cRO6cDnPzLEKflOSzQb7y1qF8S6UAQ==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.45.0", - "@wordpress/blocks": "^12.25.0", - "@wordpress/components": "^25.14.0", - "@wordpress/compose": "^6.25.0", - "@wordpress/data": "^9.18.0", - "@wordpress/deprecated": "^3.48.0", - "@wordpress/element": "^5.25.0", - "@wordpress/i18n": "^4.48.0", - "@wordpress/url": "^3.49.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/deprecated": "^3.53.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/url": "^3.54.0", "fast-deep-equal": "^3.1.3" } }, "@wordpress/shortcode": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-3.49.0.tgz", - "integrity": "sha512-4E+CQTj+MWqmYGqyPGUddKX2JgNpMIA6MrTZOQ4MEJp3VIxkLubzIwORfDZ6rlXD8PJ3kvMMivzB1MZ2svnX3Q==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-3.53.0.tgz", + "integrity": "sha512-ste35FEC3wKUmGpPCh0UaujAKUFSamcI2NEW7H+j+ODX8tgsa2fuLX4wtxPenrkoDlCblZVW4Q2tIIgBmex6XA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", @@ -34656,9 +34354,9 @@ } }, "@wordpress/style-engine": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-1.32.0.tgz", - "integrity": "sha512-0Z3DjiUuwxH9t4P085EFXo+fCT+znOYNwEf59bn6e8jRxlQx7t88ecH8hlzQNswpYj0pKBzXQCUsJsxglZYv3g==", + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-1.36.0.tgz", + "integrity": "sha512-6ANXOxOinWxMssdlhvlGoaI25okwLEx2SC6r+/JH6I7HYlnk/TSSgkpxz9t/b/sGOKrG46KzzXZT2XVb+4pDCQ==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", @@ -34666,9 +34364,9 @@ } }, "@wordpress/stylelint-config": { - "version": "21.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.31.0.tgz", - "integrity": "sha512-rorpVMYfFaNWYzg4psfUMpWLkxhD3uwWip6mf96mo/i8De4wxAz6DwKlCPIa4j74SLTiIMrdwXb2qJFNQcjQng==", + "version": "21.36.0", + "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.36.0.tgz", + "integrity": "sha512-P2Bg+Aq0jKR76wmFaNY1a4iInP/+z5+QauPD+StoHksWKvfjkYpqZ3dDLaGHucFDFF6I4UAgsDO8Avt7Q1Tl0w==", "dev": true, "requires": { "stylelint-config-recommended": "^6.0.0", @@ -34676,14 +34374,14 @@ } }, "@wordpress/sync": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@wordpress/sync/-/sync-0.11.0.tgz", - "integrity": "sha512-690oDaDUYWX3sBeHsOlXyreRFgFzVrb+GO6Vo74lUbx0zdI0sNJeX7blBSn3QvZcysN0cAvCRO1sciJinD4e5A==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@wordpress/sync/-/sync-0.15.0.tgz", + "integrity": "sha512-5pz3MTTYZ30AlpUj1oXHIgyXpWipLMZ5ueqqD+qjvo465/Eok7n7FxJv4IKwlpLG9Z65/znf1CMSO8N9dDxe3w==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", "@types/simple-peer": "^9.11.5", - "@wordpress/url": "^3.50.0", + "@wordpress/url": "^3.54.0", "import-locals": "^2.0.0", "lib0": "^0.2.42", "simple-peer": "^9.11.0", @@ -34694,28 +34392,28 @@ } }, "@wordpress/token-list": { - "version": "2.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-2.49.0.tgz", - "integrity": "sha512-TwLvEfkGqztps2xl+J57BYeJzG0lCLV418fem2VXdl2E2BCwt+d/kDggBPb4KmSdRvSO05QukZsRzPsfFRUbug==", + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-2.53.0.tgz", + "integrity": "sha512-SI6/UD8USz6USJUoF9r70/lMtp24tb9qjWCMqJp1vvtFcKgNpqHZ63SXnfE3FlYUt/Y6UurPbbsnXw6FhFEfdw==", "dev": true, "requires": { "@babel/runtime": "^7.16.0" } }, "@wordpress/undo-manager": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-0.9.0.tgz", - "integrity": "sha512-ZD6fVOdDhH8NvV/2fqjkI6W3kURzU7grWMBSZLtnSmSSPdT//1VSIxe0gcbmRvVPWLdj+TXbHifIswcJK0bHhQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-0.13.0.tgz", + "integrity": "sha512-3HY1YuQNmRdRH1ZKKLRaJlqYfWYIiBsq4I1nXKEufmwmHwMHz7nzp+bQe6erutkVran4zMzRILZPsZXpbqm//A==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/is-shallow-equal": "^4.49.0" + "@wordpress/is-shallow-equal": "^4.53.0" } }, "@wordpress/url": { - "version": "3.51.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.51.0.tgz", - "integrity": "sha512-OjucjlP1763gfKbe8lv/k3RCisyX8AfNBrhASk7JqxAj6rFhb1ZZO7YmAgB2m+WoGB5v7fkOli0FZyDqISdYyg==", + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.54.0.tgz", + "integrity": "sha512-65/2c3vzzgX4VEd90GG1tbZTN1b10NiqAa2V+a/m0Ak9RoCyAY0MtNNEa4kxCxUyN5ajpgCJCzVJIKDNVj/Fhg==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", @@ -34723,48 +34421,48 @@ } }, "@wordpress/viewport": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-5.24.0.tgz", - "integrity": "sha512-ZFsO1aGR4vW+TW1eiIWc2Nypro32hRhxBSZFEUK565T8a6smTyt1INk9m/lRIShs93w/w3MWiSH3CZW0mzDNlQ==", + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-5.30.0.tgz", + "integrity": "sha512-/OeoHXYVRpeZro3iuNh7HXjZ4HjuE9oVjsaDp1R+4G0NXAmeHd1QJFguUx3j2yrlVz8M5B0EsB7XJ4pkczMW9g==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/compose": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/element": "^5.24.0" + "@wordpress/compose": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0" } }, "@wordpress/warning": { - "version": "2.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.50.0.tgz", - "integrity": "sha512-y7Zf48roDfiPgbRAWGXDwN3C8sfbEdneGq+HvXCW6rIeGYnDLdEkpX9i7RfultkFFPVeSP3FpMKVMkto2nbqzA==", + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.53.0.tgz", + "integrity": "sha512-53O09aUJgEuGcCVTHQcxvqjeU79rHF6fw9VSZwv6lYfZTwwtxwMHGPF6hUp12NeR+bqYGsUz2Ls6gzSHaAE2Zw==", "dev": true }, "@wordpress/widgets": { - "version": "3.24.0", - "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-3.24.0.tgz", - "integrity": "sha512-bgjUoBjHKhyM2u7QrTScll7hCFDrHw0OxZWGbPXOGfE0VUgaej/d8QV5re7I+sOIi0g8+XLYQE0fwEyANt1iUg==", + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-3.30.0.tgz", + "integrity": "sha512-D56SyxK2U3OzNVy3HqfixnbWpJa9cKAaZyuK742YbNDQCHL8CgVvhHZAV4ROvi6Nk+/xtC/ndNy/WLeOKS6ROA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.44.0", - "@wordpress/block-editor": "^12.15.0", - "@wordpress/blocks": "^12.24.0", - "@wordpress/components": "^25.13.0", - "@wordpress/compose": "^6.24.0", - "@wordpress/core-data": "^6.24.0", - "@wordpress/data": "^9.17.0", - "@wordpress/element": "^5.24.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/notices": "^4.15.0", + "@wordpress/api-fetch": "^6.50.0", + "@wordpress/block-editor": "^12.21.0", + "@wordpress/blocks": "^12.30.0", + "@wordpress/components": "^27.1.0", + "@wordpress/compose": "^6.30.0", + "@wordpress/core-data": "^6.30.0", + "@wordpress/data": "^9.23.0", + "@wordpress/element": "^5.30.0", + "@wordpress/i18n": "^4.53.0", + "@wordpress/icons": "^9.44.0", + "@wordpress/notices": "^4.21.0", "classnames": "^2.3.1" } }, "@wordpress/wordcount": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/wordcount/-/wordcount-3.50.0.tgz", - "integrity": "sha512-lRfIX3B9ha//bqsUihym2BnOiAsdDQr22vdy0wZIpm5G2tFvTddCKHy0YClf52IJK0z61WqbNuF9hrvzWWxL+g==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/@wordpress/wordcount/-/wordcount-3.53.0.tgz", + "integrity": "sha512-pzx1VojKb/yh/J+GOb8+QF3UwlXuIaSXG5wurABxXPBZVk8UBmZotiEvQOZRJW1J6wn8Kta6eiwn34imR4la9A==", "dev": true, "requires": { "@babel/runtime": "^7.16.0" @@ -35146,14 +34844,14 @@ "dev": true }, "autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "version": "10.4.18", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", + "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", "dev": true, "requires": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001591", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -35508,12 +35206,6 @@ } } }, - "body-scroll-lock": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/body-scroll-lock/-/body-scroll-lock-3.1.5.tgz", - "integrity": "sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg==", - "dev": true - }, "bonjour-service": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", @@ -35561,15 +35253,15 @@ } }, "browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" } }, "bser": { @@ -35628,9 +35320,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -35750,9 +35442,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001538", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", - "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", + "version": "1.0.30001596", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz", + "integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==", "dev": true }, "capital-case": { @@ -36916,9 +36608,9 @@ "dev": true }, "deepsignal": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.3.6.tgz", - "integrity": "sha512-yjd+vtiznL6YaMptOsKnEKkPr60OEApa+LRe+Qe6Ile/RfCOrELKk/YM3qVpXFZiyOI3Ng67GDEyjAlqVc697g==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.5.0.tgz", + "integrity": "sha512-bFywDpBUUWMs576H2dgLFLLFuQ/UWXbzHfKD98MZTfGsl7+twIzvz4ihCNrRrZ/Emz3kqJaNIAp5eBWUEWhnAw==", "dev": true, "requires": {} }, @@ -37284,12 +36976,6 @@ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true }, - "dom-scroll-into-view": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-1.2.1.tgz", - "integrity": "sha512-LwNVg3GJOprWDO+QhLL1Z9MMgWe/KAFLxVWKzjRTxNSPn8/LLDIfmuG71YHznXCqaqTjvHJDYO1MEAgX6XCNbQ==", - "dev": true - }, "dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -37381,9 +37067,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.508", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.508.tgz", - "integrity": "sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg==", + "version": "1.4.698", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.698.tgz", + "integrity": "sha512-f9iZD1t3CLy1AS6vzM5EKGa6p9pRcOeEFXRFbaG2Ta+Oe7MkfRQ3fsvPYidzHe1h4i0JvIvpcY55C+B6BZNGtQ==", "dev": true }, "emittery": { @@ -37926,9 +37612,9 @@ } }, "eslint-plugin-jest": { - "version": "27.6.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.3.tgz", - "integrity": "sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA==", + "version": "27.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz", + "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==", "dev": true, "requires": { "@typescript-eslint/utils": "^5.10.0" @@ -39383,9 +39069,9 @@ } }, "http-link-header": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.1.1.tgz", - "integrity": "sha512-mW3N/rTYpCn99s1do0zx6nzFZSwLH9HGfUM4ZqLWJ16ylmYaC2v5eYGqrNTQlByx8AzUgGI+V/32gXPugs1+Sw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.1.2.tgz", + "integrity": "sha512-6qz1XhMq/ryde52SZGzVhzi3jcG2KqO16KITkupyQxvW6u7iylm0Fq7r3OpCYsc0S0ELlCiFpuxDcccUwjbEqA==", "dev": true }, "http-parser-js": { @@ -39456,9 +39142,9 @@ "dev": true }, "husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", + "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", "dev": true }, "iconv-lite": { @@ -39713,9 +39399,9 @@ } }, "ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "dev": true }, "ipaddr.js": { @@ -42679,9 +42365,9 @@ } }, "lib0": { - "version": "0.2.88", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.88.tgz", - "integrity": "sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ==", + "version": "0.2.91", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.91.tgz", + "integrity": "sha512-LRcTp8RmdHexL8olb7qErdROnJ2L6Js5Am99WQo0hiTRDWtk6vyUJJjTB6I/RcW8jwNQshM3NqH598DdhSOajA==", "dev": true, "requires": { "isomorphic.js": "^0.2.4" @@ -42927,8 +42613,7 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "lodash.escape": { "version": "4.0.1", @@ -43658,9 +43343,9 @@ "dev": true }, "node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "normalize-package-data": { @@ -43685,9 +43370,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -43833,9 +43518,9 @@ } }, "jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "lru-cache": { @@ -43848,9 +43533,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -44550,14 +44235,14 @@ } }, "playwright": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", - "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz", + "integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==", "dev": true, "peer": true, "requires": { "fsevents": "2.3.2", - "playwright-core": "1.40.1" + "playwright-core": "1.42.1" }, "dependencies": { "fsevents": { @@ -44569,9 +44254,9 @@ "peer": true }, "playwright-core": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", - "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz", + "integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==", "dev": true, "peer": true } @@ -44985,9 +44670,9 @@ "dev": true }, "preact": { - "version": "10.19.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.2.tgz", - "integrity": "sha512-UA9DX/OJwv6YwP9Vn7Ti/vF80XL+YA5H2l7BpCtUr3ya8LWHFzpiO5R+N7dN16ujpIxhekRFuOOF82bXX7K/lg==", + "version": "10.19.6", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.6.tgz", + "integrity": "sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==", "dev": true }, "prelude-ls": { @@ -44997,9 +44682,9 @@ "dev": true }, "prettier": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", - "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true }, "prettier-linter-helpers": { @@ -45513,46 +45198,6 @@ "picomatch": "^2.2.1" } }, - "reakit": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/reakit/-/reakit-1.3.11.tgz", - "integrity": "sha512-mYxw2z0fsJNOQKAEn5FJCPTU3rcrY33YZ/HzoWqZX0G7FwySp1wkCYW79WhuYMNIUFQ8s3Baob1RtsEywmZSig==", - "dev": true, - "requires": { - "@popperjs/core": "^2.5.4", - "body-scroll-lock": "^3.1.5", - "reakit-system": "^0.15.2", - "reakit-utils": "^0.15.2", - "reakit-warning": "^0.6.2" - }, - "dependencies": { - "reakit-system": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/reakit-system/-/reakit-system-0.15.2.tgz", - "integrity": "sha512-TvRthEz0DmD0rcJkGamMYx+bATwnGNWJpe/lc8UV2Js8nnPvkaxrHk5fX9cVASFrWbaIyegZHCWUBfxr30bmmA==", - "dev": true, - "requires": { - "reakit-utils": "^0.15.2" - } - }, - "reakit-utils": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/reakit-utils/-/reakit-utils-0.15.2.tgz", - "integrity": "sha512-i/RYkq+W6hvfFmXw5QW7zvfJJT/K8a4qZ0hjA79T61JAFPGt23DsfxwyBbyK91GZrJ9HMrXFVXWMovsKBc1qEQ==", - "dev": true, - "requires": {} - }, - "reakit-warning": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/reakit-warning/-/reakit-warning-0.6.2.tgz", - "integrity": "sha512-z/3fvuc46DJyD3nJAUOto6inz2EbSQTjvI/KBQDqxwB0y02HDyeP8IWOJxvkuAUGkWpeSx+H3QWQFSNiPcHtmw==", - "dev": true, - "requires": { - "reakit-utils": "^0.15.2" - } - } - } - }, "rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -46529,9 +46174,9 @@ }, "dependencies": { "ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", "dev": true } } @@ -47689,9 +47334,9 @@ } }, "typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true }, "uc.micro": { @@ -47778,9 +47423,9 @@ "dev": true }, "update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "requires": { "escalade": "^3.1.1", @@ -48007,9 +47652,9 @@ } }, "web-vitals": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.0.tgz", - "integrity": "sha512-f5YnCHVG9Y6uLCePD4tY8bO/Ge15NPEQWtvm3tPzDKygloiqtb4SVqRHBcrIAqo2ztqX5XueqDn97zHF0LdT6w==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.2.tgz", + "integrity": "sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg==", "dev": true }, "webidl-conversions": { @@ -48637,9 +48282,9 @@ } }, "yjs": { - "version": "13.6.11", - "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.11.tgz", - "integrity": "sha512-FvRRJKX9u270dOLkllGF/UDCWwmIv2Z+ucM4v1QO1TuxdmoiMnSUXH1HAcOKOrkBEhQtPTkxep7tD2DrQB+l0g==", + "version": "13.6.14", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.14.tgz", + "integrity": "sha512-D+7KcUr0j+vBCUSKXXEWfA+bG4UQBviAwP3gYBhkstkgwy5+8diOPMx0iqLIOxNo/HxaREUimZRxqHGAHCL2BQ==", "dev": true, "requires": { "lib0": "^0.2.86" diff --git a/package.json b/package.json index 6b5b660a6..5bf1f4029 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "wp-parsely", - "version": "3.13.3", + "version": "3.14.0", "private": true, "description": "The Parse.ly plugin facilitates real-time and historical analytics to your content through a platform designed and built for digital publishing.", - "author": "parsely, hbbtstar, jblz, mikeyarce, GaryJ, parsely_mike, pauarge", + "author": "parsely, hbbtstar, jblz, mikeyarce, GaryJ, parsely_mike, acicovic, mehmoodak, vaurdan", "license": "GPL-2.0-or-later", "keywords": [ "analytics", @@ -39,48 +39,50 @@ "dependencies": { "@types/js-cookie": "^3.0.6", "@wordpress/dom-ready": "^3.41.0", - "js-cookie": "^3.0.5" + "js-cookie": "^3.0.5", + "lodash.debounce": "^4.0.8" }, "devDependencies": { - "@testing-library/jest-dom": "^6.2.0", - "@testing-library/react": "^14.1.2", - "@types/jest": "^29.5.11", + "@testing-library/jest-dom": "^6.4.2", + "@testing-library/react": "^14.2.1", + "@types/jest": "^29.5.12", "@types/jest-environment-puppeteer": "^5.0.6", - "@types/wordpress__block-editor": "^11.5.9", + "@types/lodash.debounce": "^4.0.9", + "@types/wordpress__block-editor": "^11.5.11", "@types/wordpress__blocks": "^12.5.13", "@types/wordpress__components": "^23.0.11", "@types/wordpress__edit-post": "^7.5.5", "@types/wordpress__editor": "^13.6.7", "@types/wordpress__plugins": "^3.0.3", "@types/wordpress__wordcount": "^2.4.5", - "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/eslint-plugin": "^6.21.0", "@wordpress/api-fetch": "^6.38.0", "@wordpress/babel-preset-default": "^7.32.0", - "@wordpress/block-editor": "^12.17.0", + "@wordpress/block-editor": "^12.21.0", "@wordpress/blocks": "^12.26.0", - "@wordpress/components": "^25.7.0", + "@wordpress/components": "^27.0.0", "@wordpress/compose": "^6.18.0", - "@wordpress/core-data": "^6.26.0", - "@wordpress/data": "^9.19.0", - "@wordpress/e2e-test-utils": "^10.21.0", + "@wordpress/core-data": "^6.29.0", + "@wordpress/data": "^9.22.0", + "@wordpress/e2e-test-utils": "^10.24.0", "@wordpress/edit-post": "^7.24.0", - "@wordpress/editor": "^13.25.0", + "@wordpress/editor": "^13.30.0", "@wordpress/element": "^5.25.0", - "@wordpress/env": "^9.2.0", - "@wordpress/eslint-plugin": "^17.7.0", + "@wordpress/env": "^9.5.0", + "@wordpress/eslint-plugin": "^17.9.0", "@wordpress/hooks": "^3.41.0", "@wordpress/i18n": "^4.41.0", - "@wordpress/icons": "^9.38.0", - "@wordpress/plugins": "^6.18.0", - "@wordpress/scripts": "^26.19.0", - "@wordpress/url": "^3.51.0", - "@wordpress/wordcount": "^3.50.0", + "@wordpress/icons": "^9.42.0", + "@wordpress/plugins": "^6.20.0", + "@wordpress/scripts": "^27.4.0", + "@wordpress/url": "^3.52.0", + "@wordpress/wordcount": "^3.51.0", "concurrently": "^8.2.2", - "eslint-plugin-jest": "^27.6.3", - "husky": "^8.0.3", - "prettier": "^3.2.4", + "eslint-plugin-jest": "^27.9.0", + "husky": "^9.0.11", + "prettier": "^3.2.5", "ts-loader": "^9.5.1", - "typescript": "^5.3.3" + "typescript": "^5.4.2" }, "scripts": { "build": "wp-scripts build", @@ -93,7 +95,7 @@ "lint:js": "wp-scripts lint-js", "lint:pkg-json": "wp-scripts lint-pkg-json", "plugin-zip": "wp-scripts plugin-zip", - "prepare": "husky install", + "prepare": "husky", "start": "wp-scripts start", "test": "npm run test:unit", "test:e2e": "wp-scripts test-e2e", diff --git a/src/@types/assets/window.d.ts b/src/@types/assets/window.d.ts index e6560b300..617e29451 100644 --- a/src/@types/assets/window.d.ts +++ b/src/@types/assets/window.d.ts @@ -28,6 +28,7 @@ declare global { wpParselyEmptyCredentialsMessage: string; wpParselyHooks?: _Hooks; wpParselyPostsStatsResponse: string; + wpParselyPostUrl: string; wpParselySiteId: string, /** diff --git a/src/@types/gutenberg/types.ts b/src/@types/gutenberg/types.ts index 3cc1b76d7..b5a9d22ce 100644 --- a/src/@types/gutenberg/types.ts +++ b/src/@types/gutenberg/types.ts @@ -1,3 +1,6 @@ +// eslint-disable-next-line import/named +import { BlockInstance } from '@wordpress/blocks'; + /** * Defines typings for some non-exported Gutenberg functions to avoid * intellisense errors in function calls. @@ -9,4 +12,9 @@ export interface GutenbergFunction { getEditedPostAttribute( attribute: string ): string; getEditedPostContent(): string; + getSelectedBlock(): BlockInstance | null; + getBlock( clientId: string ): BlockInstance | null; + getBlocks(): BlockInstance[]; + getPermalink(): string | null; } + diff --git a/src/Endpoints/class-base-api-proxy.php b/src/Endpoints/class-base-api-proxy.php index 4e8656973..5995bb36f 100644 --- a/src/Endpoints/class-base-api-proxy.php +++ b/src/Endpoints/class-base-api-proxy.php @@ -68,12 +68,15 @@ abstract protected function generate_data( $response ): array; abstract public function get_items( WP_REST_Request $request ); /** - * Determines if there are enough permissions to call the endpoint. + * Returns whether the endpoint is available for access by the current + * user. + * + * @since 3.14.0 Renamed from `permission_callback()`. * * @return bool */ - public function permission_callback(): bool { - return $this->api->is_user_allowed_to_make_api_call(); + public function is_available_to_current_user(): bool { + return $this->api->is_available_to_current_user(); } /** @@ -90,9 +93,10 @@ public function __construct( Parsely $parsely, Remote_API_Interface $api ) { /** * Registers the endpoint's WP REST route. * - * @param string $endpoint The endpoint's route (e.g. /stats/posts). + * @param string $endpoint The endpoint's route (e.g. /stats/posts). + * @param array $methods The HTTP methods to use for the endpoint. */ - protected function register_endpoint( string $endpoint ): void { + protected function register_endpoint( string $endpoint, array $methods = array( WP_REST_Server::READABLE ) ): void { if ( ! apply_filters( 'wp_parsely_enable_' . convert_endpoint_to_filter_key( $endpoint ) . '_api_proxy', true ) ) { return; } @@ -113,11 +117,11 @@ protected function register_endpoint( string $endpoint ): void { $rest_route_args = array( array( - 'methods' => WP_REST_Server::READABLE, + 'methods' => $methods, 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'permission_callback' ), + 'permission_callback' => array( $this, 'is_available_to_current_user' ), 'args' => $get_items_args, - 'show_in_index' => $this->permission_callback(), + 'show_in_index' => $this->is_available_to_current_user(), ), ); @@ -233,7 +237,8 @@ protected function extract_post_data( stdClass $item ): array { if ( isset( $item->url ) ) { $site_id = $this->parsely->get_site_id(); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.url_to_postid_url_to_postid - $post_id = url_to_postid( $item->url ); // 0 if the post cannot be found. + $post_id = url_to_postid( $item->url ); // 0 if the post cannot be found. + $data['rawUrl'] = Parsely::get_url_with_itm_source( $item->url, null ); $data['dashUrl'] = Parsely::get_dash_url( $site_id, $item->url ); $data['id'] = Parsely::get_url_with_itm_source( $item->url, null ); // Unique. diff --git a/src/Endpoints/class-base-endpoint.php b/src/Endpoints/class-base-endpoint.php index 56cee5495..9fd161686 100644 --- a/src/Endpoints/class-base-endpoint.php +++ b/src/Endpoints/class-base-endpoint.php @@ -11,7 +11,6 @@ namespace Parsely\Endpoints; use Parsely\Parsely; -use WP_REST_Server; use function Parsely\Utils\convert_endpoint_to_filter_key; @@ -36,14 +35,13 @@ abstract class Base_Endpoint { protected const ENDPOINT = ''; /** - * Indicates whether the endpoint is public or protected behind permissions. + * The default user capability needed to access endpoints. * - * @since 3.7.0 - * @since 3.11.0 Moved from Base_Endpoint_Remote into Base_Endpoint. + * @since 3.14.0 * - * @var bool + * @var string */ - protected $is_public_endpoint = false; + protected const DEFAULT_ACCESS_CAPABILITY = 'publish_posts'; /** * Parsely Instance. @@ -55,16 +53,14 @@ abstract class Base_Endpoint { protected $parsely; /** - * User capability based on which we should allow access to the endpoint. - * - * `null` should be used for all public endpoints. + * Returns whether the endpoint is available for access by the current + * user. * - * @since 3.7.0 - * @since 3.11.0 Moved from Base_Endpoint_Remote into Base_Endpoint. + * @since 3.14.0 Replaced `is_public_endpoint`, `user_capability` and `permission_callback()`. * - * @var string|null + * @return bool */ - protected $user_capability; + abstract public function is_available_to_current_user(): bool; /** * Constructor. @@ -74,13 +70,25 @@ abstract class Base_Endpoint { * @since 3.2.0 * @since 3.7.0 Added user capability checks based on `is_public_endpoint` attribute. * @since 3.11.0 Moved from Base_Endpoint_Remote into Base_Endpoint. + * @since 3.14.0 Moved capability filters functionality outside of the constructor. */ public function __construct( Parsely $parsely ) { $this->parsely = $parsely; + } - if ( $this->is_public_endpoint ) { - $this->user_capability = null; - } else { + /** + * Returns the user capability allowing access to the endpoint, after having + * applied capability filters. + * + * `DEFAULT_ACCESS_CAPABILITY` is not passed here by default, to allow for + * a more explicit declaration in child classes. + * + * @since 3.14.0 + * + * @param string $capability The original capability allowing access. + * @return string The capability allowing access after applying the filters. + */ + protected function apply_capability_filters( string $capability ): string { /** * Filter to change the default user capability for all private endpoints. * @@ -88,11 +96,11 @@ public function __construct( Parsely $parsely ) { */ $default_user_capability = apply_filters( 'wp_parsely_user_capability_for_all_private_apis', - 'publish_posts' + $capability ); /** - * Filter to change the user capability for the specific API endpoint. + * Filter to change the user capability for the specific endpoint. * * @var string */ @@ -101,31 +109,7 @@ public function __construct( Parsely $parsely ) { $default_user_capability ); - $this->user_capability = $endpoint_specific_user_capability; - } - } - - /** - * Checks if the current user is allowed to make the API call. - * - * @since 3.7.0 - * @since 3.11.0 Moved from Base_Endpoint_Remote into Base_Endpoint. - * - * @return bool - */ - public function is_user_allowed_to_make_api_call(): bool { - // This endpoint does not require any capability checks. - if ( is_null( $this->user_capability ) ) { - return true; - } - - // The user has the required capability to access this endpoint. - // phpcs:ignore WordPress.WP.Capabilities.Undetermined - if ( current_user_can( $this->user_capability ) ) { - return true; - } - - return false; + return $endpoint_specific_user_capability; } /** @@ -164,23 +148,12 @@ public function register_endpoint( array( 'methods' => $methods, 'callback' => array( $this, $callback ), - 'permission_callback' => array( $this, 'permission_callback' ), + 'permission_callback' => array( $this, 'is_available_to_current_user' ), 'args' => $get_items_args, - 'show_in_index' => static::permission_callback(), + 'show_in_index' => static::is_available_to_current_user(), ), ); register_rest_route( 'wp-parsely/v1', $endpoint, $rest_route_args ); } - - /** - * Determines if there are enough permissions to call the endpoint. - * - * @since 3.11.0 Moved from Base_Endpoint_Remote into Base_Endpoint. - * - * @return bool - */ - public function permission_callback(): bool { - return $this->is_user_allowed_to_make_api_call(); - } } diff --git a/src/Endpoints/content-suggestions/class-suggest-meta-description-api-proxy.php b/src/Endpoints/content-suggestions/class-suggest-brief-api-proxy.php similarity index 64% rename from src/Endpoints/content-suggestions/class-suggest-meta-description-api-proxy.php rename to src/Endpoints/content-suggestions/class-suggest-brief-api-proxy.php index b86e79994..a143b92d4 100644 --- a/src/Endpoints/content-suggestions/class-suggest-meta-description-api-proxy.php +++ b/src/Endpoints/content-suggestions/class-suggest-brief-api-proxy.php @@ -1,6 +1,6 @@ suggest_meta_description_api = new Suggest_Meta_Description_API( $parsely ); - parent::__construct( $parsely, $this->suggest_meta_description_api ); + $this->suggest_brief_api = new Suggest_Brief_API( $parsely ); + parent::__construct( $parsely, $this->suggest_brief_api ); } /** @@ -50,7 +51,7 @@ public function __construct( Parsely $parsely ) { * @since 3.13.0 */ public function run(): void { - $this->register_endpoint( '/content-suggestions/suggest-meta-description' ); + $this->register_endpoint( '/content-suggestions/suggest-brief', array( 'POST' ) ); } /** @@ -94,6 +95,20 @@ public function get_items( WP_REST_Request $request ) { */ $post_title = $request->get_param( 'title' ); + /** + * The persona to be sent to the API. + * + * @var string|null $persona + */ + $persona = $request->get_param( 'persona' ); + + /** + * The style to be sent to the API. + * + * @var string|null $style + */ + $style = $request->get_param( 'style' ); + if ( null === $post_content ) { return new WP_Error( 'parsely_content_not_set', @@ -110,7 +125,15 @@ public function get_items( WP_REST_Request $request ) { ); } - $response = $this->suggest_meta_description_api->get_suggestion( $post_title, $post_content ); + if ( null === $persona ) { + $persona = 'journalist'; + } + + if ( null === $style ) { + $style = 'neutral'; + } + + $response = $this->suggest_brief_api->get_suggestion( $post_title, $post_content, $persona, $style ); if ( is_wp_error( $response ) ) { return $response; diff --git a/src/Endpoints/content-suggestions/class-write-title-api-proxy.php b/src/Endpoints/content-suggestions/class-suggest-headline-api-proxy.php similarity index 64% rename from src/Endpoints/content-suggestions/class-write-title-api-proxy.php rename to src/Endpoints/content-suggestions/class-suggest-headline-api-proxy.php index cdcd9f0d7..3d446cf07 100644 --- a/src/Endpoints/content-suggestions/class-write-title-api-proxy.php +++ b/src/Endpoints/content-suggestions/class-suggest-headline-api-proxy.php @@ -1,6 +1,6 @@ write_title_api = new Write_Title_API( $parsely ); - parent::__construct( $parsely, $this->write_title_api ); + $this->suggest_headline_api = new Suggest_Headline_API( $parsely ); + parent::__construct( $parsely, $this->suggest_headline_api ); } /** @@ -50,7 +51,7 @@ public function __construct( Parsely $parsely ) { * @since 3.12.0 */ public function run(): void { - $this->register_endpoint( '/content-suggestions/write-title' ); + $this->register_endpoint( '/content-suggestions/suggest-headline', array( 'POST' ) ); } /** @@ -75,20 +76,9 @@ protected function generate_data( $response ): array { * @return stdClass|WP_Error stdClass containing the data or a WP_Error object on failure. */ public function get_items( WP_REST_Request $request ) { - if ( false === $this->parsely->site_id_is_set() ) { - return new WP_Error( - 'parsely_site_id_not_set', - __( 'A Parse.ly Site ID must be set in site options to use this endpoint', 'wp-parsely' ), - array( 'status' => 403 ) - ); - } - - if ( false === $this->parsely->api_secret_is_set() ) { - return new WP_Error( - 'parsely_api_secret_not_set', - __( 'A Parse.ly API Secret must be set in site options to use this endpoint', 'wp-parsely' ), - array( 'status' => 403 ) - ); + $validation = $this->validate_apikey_and_secret(); + if ( is_wp_error( $validation ) ) { + return $validation; } /** @@ -114,7 +104,7 @@ public function get_items( WP_REST_Request $request ) { $limit = 3; } - $response = $this->write_title_api->get_titles( $post_content, $limit, $persona, $tone ); + $response = $this->suggest_headline_api->get_titles( $post_content, $limit, $persona, $tone ); if ( is_wp_error( $response ) ) { return $response; diff --git a/src/Endpoints/content-suggestions/class-suggest-linked-reference-api-proxy.php b/src/Endpoints/content-suggestions/class-suggest-linked-reference-api-proxy.php new file mode 100644 index 000000000..995e2b06c --- /dev/null +++ b/src/Endpoints/content-suggestions/class-suggest-linked-reference-api-proxy.php @@ -0,0 +1,138 @@ +suggest_linked_reference_api = new Suggest_Linked_Reference_API( $parsely ); + parent::__construct( $parsely, $this->suggest_linked_reference_api ); + } + + /** + * Registers the endpoint's WP REST route. + * + * @since 3.14.0 + */ + public function run(): void { + $this->register_endpoint( '/content-suggestions/suggest-linked-reference', array( 'POST' ) ); + } + + /** + * Generates the final data from the passed response. + * + * @since 3.14.0 + * + * @param array $response The response received by the proxy. + * @return array The generated data. + */ + protected function generate_data( $response ): array { + // Unused function. + return $response; + } + + /** + * Cached "proxy" to the Parse.ly API endpoint. + * + * @since 3.14.0 + * + * @param WP_REST_Request $request The request object. + * @return stdClass|WP_Error stdClass containing the data or a WP_Error + * object on failure. + */ + public function get_items( WP_REST_Request $request ) { + $validation = $this->validate_apikey_and_secret(); + if ( is_wp_error( $validation ) ) { + return $validation; + } + + /** + * The post content to be sent to the API. + * + * @var string|null $post_content + */ + $post_content = $request->get_param( 'content' ); + + /** + * The maximum amount of words of the link text. + * + * @var string|null $max_link_words + */ + $max_link_words = $request->get_param( 'max_link_words' ); + + /** + * The maximum number of links to return. + * + * @var string|null $max_links + */ + $max_links = $request->get_param( 'max_links' ); + + if ( null === $post_content ) { + return new WP_Error( + 'parsely_content_not_set', + __( 'A post content must be set to use this endpoint', 'wp-parsely' ), + array( 'status' => 403 ) + ); + } + + if ( is_numeric( $max_link_words ) ) { + $max_link_words = (int) $max_link_words; + } else { + $max_link_words = 4; + } + + if ( is_numeric( $max_links ) ) { + $max_links = (int) $max_links; + } else { + $max_links = 10; + } + + $response = $this->suggest_linked_reference_api->get_links( + $post_content, + $max_link_words, + $max_links + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + return (object) array( + 'data' => $response, + ); + } +} diff --git a/src/Endpoints/user-meta/class-base-endpoint-user-meta.php b/src/Endpoints/user-meta/class-base-endpoint-user-meta.php index ebea99691..a89a958b4 100644 --- a/src/Endpoints/user-meta/class-base-endpoint-user-meta.php +++ b/src/Endpoints/user-meta/class-base-endpoint-user-meta.php @@ -43,6 +43,15 @@ abstract class Base_Endpoint_User_Meta extends Base_Endpoint { */ protected $valid_subvalues = array(); + /** + * The current user's ID. + * + * @since 3.14.0 + * + * @var int + */ + protected $current_user_id = 0; + /** * Returns the meta entry's key. * @@ -85,6 +94,10 @@ public function __construct( Parsely $parsely ) { * @since 3.13.0 */ public function run(): void { + // Initialize the current user ID here, as doing it in the constructor + // is too early. + $this->current_user_id = get_current_user_id(); + $this->register_endpoint( static::get_route(), 'process_request', @@ -113,15 +126,26 @@ public static function get_route(): string { */ public function process_request( WP_REST_Request $request ): string { $request_method = $request->get_method(); - $user_id = get_current_user_id(); // Update the meta entry's value if the request method is PUT. if ( 'PUT' === $request_method ) { $meta_value = $request->get_json_params(); - $this->set_value( $user_id, $meta_value ); + $this->set_value( $meta_value ); } - return $this->get_value( $user_id ); + return $this->get_value(); + } + + /** + * Returns whether the endpoint is available for access by the current + * user. + * + * @since 3.14.0 + * + * @return bool + */ + public function is_available_to_current_user(): bool { + return current_user_can( 'edit_user', $this->current_user_id ); } /** @@ -129,12 +153,11 @@ public function process_request( WP_REST_Request $request ): string { * * @since 3.13.0 * - * @param int $user_id The user ID to which the meta entry is assigned. * @return string The meta entry's value as JSON. */ - protected function get_value( int $user_id ): string { + protected function get_value(): string { $meta_key = $this->get_meta_key(); - $meta_value = get_user_meta( $user_id, $meta_key, true ); + $meta_value = get_user_meta( $this->current_user_id, $meta_key, true ); if ( ! is_array( $meta_value ) || 0 === count( $meta_value ) ) { $meta_value = $this->default_value; @@ -150,15 +173,14 @@ protected function get_value( int $user_id ): string { * * @since 3.13.0 * - * @param int $user_id The user ID to which the meta entry is assigned. * @param array $meta_value The value to set the meta entry to. * @return bool Whether updating the meta entry's value was successful. */ - protected function set_value( int $user_id, array $meta_value ): bool { + protected function set_value( array $meta_value ): bool { $sanitized_value = $this->sanitize_value( $meta_value ); $update_meta = update_user_meta( - $user_id, + $this->current_user_id, $this->get_meta_key(), $sanitized_value ); @@ -174,6 +196,7 @@ protected function set_value( int $user_id, array $meta_value ): bool { * Sanitizes the passed meta value. * * @since 3.13.0 + * @since 3.14.0 Added support for nested arrays. * * @param array $meta_value The meta value to sanitize. * @return array The sanitized meta as an array of subvalues. @@ -189,11 +212,12 @@ protected function sanitize_value( array $meta_value ): array { continue; } + // Use the enhanced sanitize_subvalue method. $sanitized_value[ $key ] = $this->sanitize_subvalue( $key, $value ); } // If not all subvalues are set, return the default meta value. - if ( 0 !== count( array_diff_key( $this->valid_subvalues, $sanitized_value ) ) ) { + if ( count( array_diff_key( $this->valid_subvalues, $sanitized_value ) ) !== 0 ) { return $this->default_value; } @@ -204,22 +228,36 @@ protected function sanitize_value( array $meta_value ): array { * Sanitizes the passed subvalue. * * @since 3.13.0 + * @since 3.14.0 Added support for nested arrays. * * @param string $key The subvalue's key. * @param mixed $value The value to sanitize. * @return mixed The sanitized subvalue. */ protected function sanitize_subvalue( string $key, $value ) { + // Handle nested arrays recursively. + if ( is_array( $value ) ) { + $sanitized_array = array(); + foreach ( $value as $subkey => $subvalue ) { + // Sanitize keys of nested arrays. + $sanitized_subkey = sanitize_text_field( $subkey ); + // Recursively sanitize each value in the nested array. + $sanitized_array[ $sanitized_subkey ] = $this->sanitize_subvalue( $sanitized_subkey, $subvalue ); + } + return $sanitized_array; + } + + // Sanitize simple values. if ( is_string( $value ) ) { $value = sanitize_text_field( $value ); } // Allow any value when no valid subvalues are given. - if ( 0 === count( $this->valid_subvalues[ $key ] ) ) { + if ( ! isset( $this->valid_subvalues[ $key ] ) || count( $this->valid_subvalues[ $key ] ) === 0 ) { return $value; } - // If the value is not valid, use the default value. + // Use default value if the actual value is not valid. if ( ! in_array( $value, $this->valid_subvalues[ $key ], true ) ) { $value = $this->default_value[ $key ]; } diff --git a/src/Endpoints/user-meta/class-editor-sidebar-settings-endpoint.php b/src/Endpoints/user-meta/class-editor-sidebar-settings-endpoint.php index db85feb67..b27c72a4b 100644 --- a/src/Endpoints/user-meta/class-editor-sidebar-settings-endpoint.php +++ b/src/Endpoints/user-meta/class-editor-sidebar-settings-endpoint.php @@ -41,49 +41,65 @@ protected function get_meta_key(): string { */ protected function get_subvalues_specs(): array { return array( - 'PerformanceDetailsOpen' => array( - 'values' => array( true, false ), - 'default' => true, + 'InitialTabName' => array( + 'values' => array( 'tools', 'performance' ), + 'default' => 'tools', + ), + 'PerformanceStatsSettings' => array( + 'values' => array( + 'Period' => array( '10m', '1h', '2h', '4h', '24h', '7d', '30d' ), + 'VisiblePanels' => array( 'overview', 'categories', 'referrers' ), + 'VisibleDataPoints' => array( 'views', 'visitors', 'avgEngaged', 'recirculation' ), + ), + 'default' => array( + 'Period' => '7d', + 'VisiblePanels' => array( 'overview', 'categories', 'referrers' ), + 'VisibleDataPoints' => array( 'views', 'visitors', 'avgEngaged', 'recirculation' ), + ), ), - 'RelatedTopPostsFilterBy' => array( + 'RelatedPostsFilterBy' => array( 'values' => array( 'unavailable', 'tag', 'section', 'author' ), 'default' => 'unavailable', ), - 'RelatedTopPostsFilterValue' => array( + 'RelatedPostsFilterValue' => array( 'values' => array(), 'default' => '', ), - 'RelatedTopPostsOpen' => array( - 'values' => array( true, false ), - 'default' => false, - ), - 'SettingsMetric' => array( + 'RelatedPostsMetric' => array( 'values' => array( 'views', 'avg_engaged' ), 'default' => 'views', ), - 'SettingsOpen' => array( + 'RelatedPostsOpen' => array( 'values' => array( true, false ), - 'default' => true, + 'default' => false, ), - 'SettingsPeriod' => array( + 'RelatedPostsPeriod' => array( 'values' => array( '10m', '1h', '2h', '4h', '24h', '7d', '30d' ), 'default' => '7d', ), - 'TitleSuggestionsOpen' => array( - 'values' => array( true, false ), - 'default' => false, + 'SmartLinkingMaxLinks' => array( + 'values' => array(), + 'default' => 10, ), - 'TitleSuggestionsPersona' => array( + 'SmartLinkingMaxLinkWords' => array( 'values' => array(), - 'default' => 'journalist', + 'default' => 4, ), - 'TitleSuggestionsSettingsOpen' => array( + 'SmartLinkingOpen' => array( 'values' => array( true, false ), 'default' => false, ), - 'TitleSuggestionsTone' => array( - 'values' => array(), - 'default' => 'neutral', + 'TitleSuggestionsSettings' => array( + 'values' => array( + 'Open' => array( true, false ), + 'Persona' => array(), + 'Tone' => array(), + ), + 'default' => array( + 'Open' => false, + 'Persona' => 'journalist', + 'Tone' => 'neutral', + ), ), ); } diff --git a/src/Metadata/class-metadata-builder.php b/src/Metadata/class-metadata-builder.php index 96377bfe6..3b842b313 100644 --- a/src/Metadata/class-metadata-builder.php +++ b/src/Metadata/class-metadata-builder.php @@ -11,6 +11,10 @@ namespace Parsely\Metadata; use Parsely\Parsely; +use WP_Post; +use WP_User; + +use function Parsely\Utils\get_default_category; /** * Abstract class that implements modular builders for Metadata. @@ -20,6 +24,8 @@ * * @since 1.0.0 * @since 3.3.0 Logic extracted from Parsely\Parsely class to separate file/class. + * + * @phpstan-import-type Parsely_Options from Parsely */ abstract class Metadata_Builder { /** @@ -66,7 +72,7 @@ protected function build_basic(): void { } /** - * Populates the `url` field in the metadata object by getting the current page's URL. + * Populates the url field in the metadata object. * * @since 3.4.0 */ @@ -74,6 +80,214 @@ protected function build_url(): void { $this->metadata['url'] = $this->get_current_url(); } + /** + * Populates the @type field in the metadata object. + * + * @param WP_Post $post The post/page for which to populate the field. + * @param string $parsely_type Parse.ly post type. Can be 'post' or 'non-post'. + * + * @since 3.4.0 + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + */ + protected function build_type( WP_Post $post, string $parsely_type ): void { + $default_type = 'post' === $parsely_type ? 'NewsArticle' : 'WebPage'; + + /** + * Filters the JSON-LD @type. + * + * @since 2.5.0 + * + * @param string $jsonld_type JSON-LD @type value, default is NewsArticle. + * @param int $id Post ID. + * @param string $post_type The Post type in WordPress. + */ + $type = apply_filters( 'wp_parsely_post_type', $default_type, $post->ID, $post->post_type ); + $supported_types = $this->parsely->get_all_supported_types(); + + // Validate type before passing it further as an invalid type will not be recognized by Parse.ly. + if ( ! in_array( $type, $supported_types, true ) ) { + $error = sprintf( + /* translators: 1: JSON @type like NewsArticle, 2: URL */ + __( '@type %1$s is not supported by Parse.ly. Please use a type mentioned in %2$s', 'wp-parsely' ), + $type, + 'https://docs.parse.ly/metadata-jsonld/#distinguishing-between-posts-and-non-posts-pages' + ); + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error + trigger_error( esc_html( $error ), E_USER_WARNING ); + $type = $default_type; + } + + $this->metadata['@type'] = $type; + } + + /** + * Populates the mainEntityOfPage field in the metadata object. + * + * @param string $build_url_type Parse.ly post type to use for building the + * URL. Can be 'post' or 'non-post'. + * + * @since 3.4.0 + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + */ + protected function build_main_entity( string $build_url_type ): void { + $this->metadata['mainEntityOfPage'] = array( + '@type' => 'WebPage', + '@id' => $this->get_current_url( $build_url_type ), + ); + } + + /** + * Populates the author and creator fields in the metadata object. + * + * @since 3.4.0 + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param WP_Post $post The post/page for which to populate the fields. + */ + protected function build_author( WP_Post $post ): void { + $authors = $this->get_author_names( $post ); + $author_objects = array(); + foreach ( $authors as $author ) { + $author_tag = array( + '@type' => 'Person', + 'name' => $author, + ); + $author_objects[] = $author_tag; + } + $this->metadata['author'] = $author_objects; + $this->metadata['creator'] = $authors; + } + + /** + * Populates the publisher field in the metadata object. + * + * @since 3.4.0 + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + */ + protected function build_publisher(): void { + $this->metadata['publisher'] = array( + '@type' => 'Organization', + 'name' => get_bloginfo( 'name' ), + 'logo' => $this->parsely->get_options()['logo'], + ); + } + + /** + * Populates all the fields related to time in the metadata object. + * + * @param WP_Post $post The post/page for which to populate the field. + * + * @since 3.0.2 + * @since 3.3.0 Moved to class-metadata. + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + */ + protected function build_metadata_post_times( WP_Post $post ): void { + $date_format = 'Y-m-d\TH:i:s\Z'; + $post_created_gmt = get_post_time( $date_format, true, $post ); + + if ( false === $post_created_gmt ) { + return; + } + + $this->metadata['dateCreated'] = $post_created_gmt; + $this->metadata['datePublished'] = $post_created_gmt; + $this->metadata['dateModified'] = $post_created_gmt; + + $post_modified_gmt = get_post_modified_time( $date_format, true, $post ); + + if ( false !== $post_modified_gmt && $post_modified_gmt > $post_created_gmt ) { + $this->metadata['dateModified'] = $post_modified_gmt; + } + } + + /** + * Populates the articleSection field in the metadata object. + * + * @since 3.4.0 + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param WP_Post $post The post/page for which to populate the field. + */ + protected function build_article_section( WP_Post $post ): void { + $this->metadata['articleSection'] = $this->get_category_name( $post, $this->parsely->get_options() ); + } + + /** + * Populates the keywords field in the metadata object. + * + * @since 3.4.0 + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param WP_Post $post The post/page for which to populate the field. + */ + protected function build_keywords( WP_Post $post ): void { + $options = $this->parsely->get_options(); + $tags = $this->get_tags( $post->ID ); + if ( $options['cats_as_tags'] ) { + $tags = array_merge( $tags, $this->get_categories( $post->ID ) ); + // add custom taxonomy values. + $tags = array_merge( $tags, $this->get_custom_taxonomy_values( $post ) ); + } + // The function 'mb_strtolower' is not enabled by default in php, so this check + // falls back to the native php function 'strtolower' if necessary. + if ( function_exists( 'mb_strtolower' ) ) { + $lowercase_callback = 'mb_strtolower'; + } else { + $lowercase_callback = 'strtolower'; + } + if ( $options['lowercase_tags'] ) { + $tags = array_map( $lowercase_callback, $tags ); + } + + /** + * Filters the post tags that are used as metadata keywords. + * + * @since 1.8.0 + * + * @param array $tags Post tags. + * @param int $ID Post ID. + */ + $tags = apply_filters( 'wp_parsely_post_tags', $tags, $post->ID ); + $tags = array_map( array( $this, 'clean_value' ), $tags ); + + $this->metadata['keywords'] = array_values( array_unique( $tags ) ); + } + + /** + * Populates the thumbnailUrl field in the metadata object. + * + * @param WP_Post $post The post/page for which to populate the field. + * + * @since 3.4.0 + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + */ + protected function build_thumbnail_url( WP_Post $post ): void { + $thumb_url = get_the_post_thumbnail_url( $post, 'thumbnail' ); + if ( ! is_string( $thumb_url ) ) { + $thumb_url = ''; + } + $this->metadata['thumbnailUrl'] = $thumb_url; + } + + /** + * Populates the image field in the metadata object. + * + * @param WP_Post $post The post/page for which to populate the field. + * + * @since 3.4.0 + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + */ + protected function build_image( WP_Post $post ): void { + $image_url = get_the_post_thumbnail_url( $post, 'full' ); + if ( ! is_string( $image_url ) ) { + $image_url = ''; + } + $this->metadata['image'] = array( + '@type' => 'ImageObject', + 'url' => $image_url, + ); + } + /** * Sanitizes string content. * @@ -134,4 +348,344 @@ protected function get_current_url( string $parsely_type = 'non-post', int $post ? str_replace( 'http://', 'https://', $url ) : str_replace( 'https://', 'http://', $url ); } + + /** + * Returns a properly cleaned category/taxonomy value and will optionally + * use the top-level category/taxonomy value, if so instructed via the + * use_top_level_cats option. + * + * @since 3.3.0 Moved to class-metadata. + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param WP_Post $post_obj The object for the post. + * @param Parsely_Options $parsely_options The parsely options. + * @return string Cleaned category name for the post in question. + */ + private function get_category_name( WP_Post $post_obj, $parsely_options ): string { + $taxonomy_dropdown_choice = get_the_terms( $post_obj->ID, $parsely_options['custom_taxonomy_section'] ); + // Get top-level taxonomy name for chosen taxonomy and assign to $parent_name; it will be used + // as the category value if 'use_top_level_cats' option is checked. + // Assign as the default category name if no value is checked for the chosen taxonomy. + $category_name = get_cat_name( get_default_category() ); + if ( false !== $taxonomy_dropdown_choice && ! is_wp_error( $taxonomy_dropdown_choice ) ) { + if ( $parsely_options['use_top_level_cats'] ) { + $first_term = array_shift( $taxonomy_dropdown_choice ); + if ( null !== $first_term ) { + $term_name = $this->get_top_level_term( $first_term->term_id, $first_term->taxonomy ); + } + } else { + $term_name = $this->get_bottom_level_term( $post_obj->ID, $parsely_options['custom_taxonomy_section'] ); + } + + if ( isset( $term_name ) && is_string( $term_name ) && 0 < strlen( $term_name ) ) { + $category_name = $term_name; + } + } + + /** + * Filters the constructed category name. + * + * @since 1.8.0 + * + * @param string $category Category name. + * @param WP_Post $post_obj Post object. + * @param array $parsely_options The Parsely options. + */ + $category_name = apply_filters( 'wp_parsely_post_category', $category_name, $post_obj, $parsely_options ); + + return $this->clean_value( $category_name ); + } + + /** + * Returns the top-most category/taxonomy value in a hierarchy given a + * taxonomy value's ID. + * + * (WordPress calls taxonomy values 'terms'). + * + * @since 3.3.0 Moved to class-metadata. + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param int $term_id The ID of the top level term. + * @param string $taxonomy_name The name of the taxonomy. + * @return string|false $parent The top level name of the category/taxonomy. + */ + private function get_top_level_term( int $term_id, string $taxonomy_name ) { + $parent = get_term_by( 'id', $term_id, $taxonomy_name ); + + while ( false !== $parent && isset( $parent->parent ) && 0 !== $parent->parent ) { + $parent = get_term_by( 'id', $parent->parent, $taxonomy_name ); + } + + return $parent->name ?? false; + } + + /** + * Returns the bottom-most category/taxonomy value in a hierarchy given a + * post ID. + * + * (WordPress calls taxonomy values 'terms'). + * + * @since 3.3.0 Moved to class-metadata. + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param int $post_id The post id you're interested in. + * @param string $taxonomy_name The name of the taxonomy. + * @return string Name of the custom taxonomy. + */ + private function get_bottom_level_term( int $post_id, string $taxonomy_name ): string { + $terms = get_the_terms( $post_id, $taxonomy_name ); + + if ( ! is_array( $terms ) ) { + return ''; + } + + $term_ids = wp_list_pluck( $terms, 'term_id' ); + $parents = array_filter( wp_list_pluck( $terms, 'parent' ) ); + + // Get array of IDs of terms which are not parents. + $term_ids_not_parents = array_diff( $term_ids, $parents ); + // Get corresponding term objects, which are mapped to array index keys. + $terms_not_parents = array_intersect_key( $terms, $term_ids_not_parents ); + // remove array index keys. + $terms_not_parents_cleaned = array_values( $terms_not_parents ); + + if ( isset( $terms_not_parents_cleaned[0] ) ) { + // If you assign multiple child terms in a custom taxonomy, will only return the first. + return $terms_not_parents_cleaned[0]->name; + } + + return ''; + } + + /** + * Retrieves all the authors for a post as an array. Can include multiple + * authors if the Co-Authors Plus plugin is in use. + * + * @since 3.3.0 Moved to class-metadata. + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param WP_Post $post The post object. + * @return array + */ + private function get_author_names( WP_Post $post ): array { + $authors = $this->get_coauthor_names( $post->ID ); + if ( 0 === count( $authors ) ) { + $post_author = get_user_by( 'id', $post->post_author ); + if ( false !== $post_author ) { + $authors = array( $post_author ); + } + } + + /** + * Filters the list of author WP_User objects for a post. + * + * @since 1.14.0 + * + * @param array $authors One or more authors as WP_User objects. + * @param WP_Post $post Post object. + */ + $authors = apply_filters( 'wp_parsely_pre_authors', $authors, $post ); + + // Getting the author name for each author. + $authors = array_map( array( $this, 'get_author_name' ), $authors ); + + /** + * Filters the list of author names for a post. + * + * @since 1.14.0 + * + * @param array $authors One or more author names. + * @param WP_Post $post Post object. + */ + $authors = apply_filters( 'wp_parsely_post_authors', $authors, $post ); + + return array_map( array( $this, 'clean_value' ), $authors ); + } + + /** + * Returns a list of coauthors for a post assuming the Co-Authors Plus plugin + * is installed. + * + * Borrowed from + * https://github.com/Automattic/Co-Authors-Plus/blob/master/template-tags.php#L3-35 + * + * @since 3.3.0 Moved to class-metadata. + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param int $post_id The ID of the post. + * @return array List of coauthors, or an empty array if the Co-Authors Plus plugin is not active. + */ + private function get_coauthor_names( int $post_id ): array { + $coauthors = array(); + if ( class_exists( 'coauthors_plus' ) ) { + global $post, $post_ID, $coauthors_plus; + + if ( $post_id <= 0 && $post_ID ) { + $post_id = $post_ID; + } + + if ( ! $post_id && $post ) { + $post_id = $post->ID; + } + + if ( $post_id ) { + $coauthor_terms = get_the_terms( $post_id, $coauthors_plus->coauthor_taxonomy ); + + if ( is_array( $coauthor_terms ) ) { + foreach ( $coauthor_terms as $coauthor ) { + $coauthor_slug = preg_replace( '#^cap-#', '', $coauthor->slug ); + $post_author = $coauthors_plus->get_coauthor_by( 'user_nicename', $coauthor_slug ); + // In case the user has been deleted while plugin was deactivated. + if ( false !== $post_author ) { + $coauthors[] = new WP_User( $post_author ); + } + } + } elseif ( ! $coauthors_plus->force_guest_authors ) { + if ( $post && $post_id === $post->ID ) { + $post_author = get_userdata( $post->post_author ); + } + if ( isset( $post_author ) && false !== $post_author ) { + $coauthors[] = $post_author; + } + } + // The empty else case is because if we force guest authors, we don't ever care what value wp_posts.post_author has. + } + } + return $coauthors; + } + + /** + * Determines author name from display name, falling back to firstname + * lastname, then nickname and finally the nicename. + * + * @since 3.3.0 Moved to class-metadata. + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param ?WP_User $author The author of the post. + * @return string An author name. + */ + private function get_author_name( ?WP_User $author ): string { + // Gracefully handle situation where no author is available. + if ( null === $author ) { + return ''; + } + + if ( '' !== $author->display_name ) { + return $author->display_name; + } + + $author_name = $author->user_firstname . ' ' . $author->user_lastname; + if ( ' ' !== $author_name ) { + return $author_name; + } + + if ( '' !== $author->nickname ) { + return $author->nickname; + } + + if ( '' !== $author->user_nicename ) { + return $author->user_nicename; + } + + return ''; + } + + /** + * Returns the tags associated with this page or post. + * + * @since 3.3.0 Moved to class-metadata. + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param int $post_id The ID of the post you're trying to get tags for. + * @return array The tags of the post represented by the post id. + */ + private function get_tags( int $post_id ): array { + /** + * Variable. + * + * @var array<\WP_Term|null>|\WP_Error + */ + $post_tags = wp_get_post_tags( $post_id ); + $tags = array(); + + if ( ! is_wp_error( $post_tags ) ) { + foreach ( $post_tags as $wp_tag ) { + if ( null !== $wp_tag ) { + $tags[] = $wp_tag->name; + } + } + } + + return $tags; + } + + /** + * Returns an array of all the child categories for the current post. + * + * @since 3.3.0 Moved to class-metadata. + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param int $post_id The ID of the post you're trying to get categories for. + * @param string $delimiter What character will delimit the categories. + * @return array All the child categories of the current post. + */ + private function get_categories( int $post_id, string $delimiter = '/' ): array { + $tags = array(); + foreach ( get_the_category( $post_id ) as $category ) { + $hierarchy = get_category_parents( $category->term_id, false, $delimiter ); + if ( ! is_wp_error( $hierarchy ) ) { + $tags[] = rtrim( $hierarchy, '/' ); + } + } + // Take last element in the hierarchy, a string representing the full + // parent->child tree, and split it into individual category names. + $last_tag = end( $tags ); + if ( false !== $last_tag ) { + $tags = explode( '/', $last_tag ); + } + + // Remove default category name from tags if needed. + $default_category_name = get_cat_name( get_default_category() ); + return array_diff( $tags, array( $default_category_name ) ); + } + + /** + * Gets all term names from all custom taxonomies assigned to a post. + * + * @since 3.3.0 Moved to class-metadata. + * @since 3.4.0 Moved to class-post-builder. + * @since 3.14.0 Moved from `Post_Builder` to `Metadata_Builder`. + * + * @param WP_Post $post_obj The post object to find the terms for. + * @return array Term names. + */ + private function get_custom_taxonomy_values( WP_Post $post_obj ): array { + // Filter out default WordPress taxonomies. + $all_taxonomies = array_diff( + get_taxonomies(), + array( 'post_tag', 'nav_menu', 'author', 'link_category', 'post_format' ) + ); + + /** + * Filters the taxonomies. + * + * @since 3.11.0 + * + * @param array $all_taxonomies Taxonomies. + * @param WP_Post $post_obj Post object. + */ + $all_taxonomies = apply_filters( 'wp_parsely_custom_taxonomies', $all_taxonomies, $post_obj ); + $all_values = array(); + + foreach ( $all_taxonomies as $taxonomy ) { + $custom_taxonomy_objects = get_the_terms( $post_obj->ID, $taxonomy ); + if ( is_array( $custom_taxonomy_objects ) ) { + foreach ( $custom_taxonomy_objects as $custom_taxonomy_object ) { + $all_values[] = $custom_taxonomy_object->name; + } + } + } + + return $all_values; + } } diff --git a/src/Metadata/class-page-builder.php b/src/Metadata/class-page-builder.php index 5f55913bd..373f573c4 100644 --- a/src/Metadata/class-page-builder.php +++ b/src/Metadata/class-page-builder.php @@ -51,11 +51,23 @@ public function get_metadata(): array { $this->build_headline(); $this->build_url(); + if ( true === $this->parsely->get_options()['full_metadata_in_non_posts'] ) { + $this->build_type( $this->post, 'non-post' ); + $this->build_main_entity( 'post' ); + $this->build_thumbnail_url( $this->post ); + $this->build_image( $this->post ); + $this->build_article_section( $this->post ); + $this->build_author( $this->post ); + $this->build_publisher(); + $this->build_keywords( $this->post ); + $this->build_metadata_post_times( $this->post ); + } + return $this->metadata; } /** - * Populates the `headline` field in the metadata object. + * Populates the headline field in the metadata object. * * @since 3.4.0 */ @@ -64,7 +76,7 @@ private function build_headline(): void { } /** - * Populates the `url` field in the metadata object by getting the current page's URL. + * Populates the url field in the metadata object by getting the current page's URL. * * @since 3.4.0 */ diff --git a/src/Metadata/class-post-builder.php b/src/Metadata/class-post-builder.php index 5247d6c1f..8f1746684 100644 --- a/src/Metadata/class-post-builder.php +++ b/src/Metadata/class-post-builder.php @@ -12,17 +12,12 @@ use Parsely\Parsely; use WP_Post; -use WP_User; - -use function Parsely\Utils\get_default_category; /** * Implements abstract Metadata Builder class to generate the metadata array * for a post page. * * @since 3.4.0 - * - * @phpstan-import-type Parsely_Options from Parsely */ class Post_Builder extends Metadata_Builder { /** @@ -56,21 +51,21 @@ public function get_metadata(): array { $this->build_headline(); $this->build_url(); - $this->build_type(); - $this->build_main_entity(); - $this->build_thumbnail_url(); - $this->build_image(); - $this->build_article_section(); - $this->build_author(); + $this->build_type( $this->post, 'post' ); + $this->build_main_entity( 'post' ); + $this->build_thumbnail_url( $this->post ); + $this->build_image( $this->post ); + $this->build_article_section( $this->post ); + $this->build_author( $this->post ); $this->build_publisher(); - $this->build_keywords(); - $this->build_metadata_post_times(); + $this->build_keywords( $this->post ); + $this->build_metadata_post_times( $this->post ); return $this->metadata; } /** - * Populates the `headline` field in the metadata object. + * Populates the headline field in the metadata object. * * @since 3.4.0 */ @@ -79,518 +74,11 @@ private function build_headline(): void { } /** - * Populates the `url` field in the metadata object by getting the current page's URL. + * Populates the url field in the metadata object by getting the current page's URL. * * @since 3.4.0 */ protected function build_url(): void { $this->metadata['url'] = $this->get_current_url( 'post', $this->post->ID ); } - - /** - * Populates the `@type` field in the metadata object. - * - * @since 3.4.0 - */ - private function build_type(): void { - /** - * Filters the JSON-LD @type. - * - * @since 2.5.0 - * - * @param string $jsonld_type JSON-LD @type value, default is NewsArticle. - * @param int $id Post ID. - * @param string $post_type The Post type in WordPress. - */ - $type = apply_filters( 'wp_parsely_post_type', 'NewsArticle', $this->post->ID, $this->post->post_type ); - $supported_types = $this->parsely->get_all_supported_types(); - - // Validate type before passing it further as an invalid type will not be recognized by Parse.ly. - if ( ! in_array( $type, $supported_types, true ) ) { - $error = sprintf( - /* translators: 1: JSON @type like NewsArticle, 2: URL */ - __( '@type %1$s is not supported by Parse.ly. Please use a type mentioned in %2$s', 'wp-parsely' ), - $type, - 'https://docs.parse.ly/metadata-jsonld/#distinguishing-between-posts-and-non-posts-pages' - ); - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error - trigger_error( esc_html( $error ), E_USER_WARNING ); - $type = 'NewsArticle'; - } - - $this->metadata['@type'] = $type; - } - - /** - * Populates the `mainEntityOfPage` field in the metadata object. - * - * @since 3.4.0 - */ - private function build_main_entity(): void { - $this->metadata['mainEntityOfPage'] = array( - '@type' => 'WebPage', - '@id' => $this->get_current_url( 'post' ), - ); - } - - /** - * Populates the `thumbnailUrl` field in the metadata object. - * - * @since 3.4.0 - */ - private function build_thumbnail_url(): void { - $thumb_url = get_the_post_thumbnail_url( $this->post, 'thumbnail' ); - if ( ! is_string( $thumb_url ) ) { - $thumb_url = ''; - } - $this->metadata['thumbnailUrl'] = $thumb_url; - } - - /** - * Populates the `image` field in the metadata object. - * - * @since 3.4.0 - */ - private function build_image(): void { - $image_url = get_the_post_thumbnail_url( $this->post, 'full' ); - if ( ! is_string( $image_url ) ) { - $image_url = ''; - } - $this->metadata['image'] = array( - '@type' => 'ImageObject', - 'url' => $image_url, - ); - } - - /** - * Populates the `articleSection` field in the metadata object. - * - * @since 3.4.0 - */ - private function build_article_section(): void { - $this->metadata['articleSection'] = $this->get_category_name( $this->post, $this->parsely->get_options() ); - } - - /** - * Populates the `author` and `creator` fields in the metadata object. - * - * @since 3.4.0 - */ - private function build_author(): void { - $authors = $this->get_author_names( $this->post ); - $author_objects = array(); - foreach ( $authors as $author ) { - $author_tag = array( - '@type' => 'Person', - 'name' => $author, - ); - $author_objects[] = $author_tag; - } - $this->metadata['author'] = $author_objects; - $this->metadata['creator'] = $authors; - } - - /** - * Populates the `publisher` field in the metadata object. - * - * @since 3.4.0 - */ - private function build_publisher(): void { - $this->metadata['publisher'] = array( - '@type' => 'Organization', - 'name' => get_bloginfo( 'name' ), - 'logo' => $this->parsely->get_options()['logo'], - ); - } - - /** - * Populates the `keywords` field in the metadata object. - * - * @since 3.4.0 - */ - private function build_keywords(): void { - $options = $this->parsely->get_options(); - $tags = $this->get_tags( $this->post->ID ); - if ( $options['cats_as_tags'] ) { - $tags = array_merge( $tags, $this->get_categories( $this->post->ID ) ); - // add custom taxonomy values. - $tags = array_merge( $tags, $this->get_custom_taxonomy_values( $this->post ) ); - } - // The function 'mb_strtolower' is not enabled by default in php, so this check - // falls back to the native php function 'strtolower' if necessary. - if ( function_exists( 'mb_strtolower' ) ) { - $lowercase_callback = 'mb_strtolower'; - } else { - $lowercase_callback = 'strtolower'; - } - if ( $options['lowercase_tags'] ) { - $tags = array_map( $lowercase_callback, $tags ); - } - - /** - * Filters the post tags that are used as metadata keywords. - * - * @since 1.8.0 - * - * @param array $tags Post tags. - * @param int $ID Post ID. - */ - $tags = apply_filters( 'wp_parsely_post_tags', $tags, $this->post->ID ); - $tags = array_map( array( $this, 'clean_value' ), $tags ); - - $this->metadata['keywords'] = array_values( array_unique( $tags ) ); - } - - /** - * Sets all metadata values related to post time. - * - * @since 3.0.2 - * @since 3.3.0 Moved to class-metadata. - */ - private function build_metadata_post_times(): void { - $date_format = 'Y-m-d\TH:i:s\Z'; - $post_created_gmt = get_post_time( $date_format, true, $this->post ); - - if ( false === $post_created_gmt ) { - return; - } - - $this->metadata['dateCreated'] = $post_created_gmt; - $this->metadata['datePublished'] = $post_created_gmt; - $this->metadata['dateModified'] = $post_created_gmt; - - $post_modified_gmt = get_post_modified_time( $date_format, true, $this->post ); - - if ( false !== $post_modified_gmt && $post_modified_gmt > $post_created_gmt ) { - $this->metadata['dateModified'] = $post_modified_gmt; - } - } - - /** - * Returns a properly cleaned category/taxonomy value and will optionally - * use the top-level category/taxonomy value, if so instructed via the - * `use_top_level_cats` option. - * - * @since 3.3.0 Moved to class-metadata. - * - * @param WP_Post $post_obj The object for the post. - * @param Parsely_Options $parsely_options The parsely options. - * @return string Cleaned category name for the post in question. - */ - private function get_category_name( WP_Post $post_obj, $parsely_options ): string { - $taxonomy_dropdown_choice = get_the_terms( $post_obj->ID, $parsely_options['custom_taxonomy_section'] ); - // Get top-level taxonomy name for chosen taxonomy and assign to $parent_name; it will be used - // as the category value if 'use_top_level_cats' option is checked. - // Assign as the default category name if no value is checked for the chosen taxonomy. - $category_name = get_cat_name( get_default_category() ); - if ( false !== $taxonomy_dropdown_choice && ! is_wp_error( $taxonomy_dropdown_choice ) ) { - if ( $parsely_options['use_top_level_cats'] ) { - $first_term = array_shift( $taxonomy_dropdown_choice ); - if ( null !== $first_term ) { - $term_name = $this->get_top_level_term( $first_term->term_id, $first_term->taxonomy ); - } - } else { - $term_name = $this->get_bottom_level_term( $post_obj->ID, $parsely_options['custom_taxonomy_section'] ); - } - - if ( isset( $term_name ) && is_string( $term_name ) && 0 < strlen( $term_name ) ) { - $category_name = $term_name; - } - } - - /** - * Filters the constructed category name. - * - * @since 1.8.0 - * - * @param string $category Category name. - * @param WP_Post $post_obj Post object. - * @param array $parsely_options The Parsely options. - */ - $category_name = apply_filters( 'wp_parsely_post_category', $category_name, $post_obj, $parsely_options ); - - return $this->clean_value( $category_name ); - } - - /** - * Returns the top-most category/taxonomy value in a hierarchy given a - * taxonomy value's ID. - * - * (WordPress calls taxonomy values 'terms'). - * - * @since 3.3.0 Moved to class-metadata. - * - * @param int $term_id The ID of the top level term. - * @param string $taxonomy_name The name of the taxonomy. - * @return string|false $parent The top level name of the category / taxonomy. - */ - private function get_top_level_term( int $term_id, string $taxonomy_name ) { - $parent = get_term_by( 'id', $term_id, $taxonomy_name ); - - while ( false !== $parent && isset( $parent->parent ) && 0 !== $parent->parent ) { - $parent = get_term_by( 'id', $parent->parent, $taxonomy_name ); - } - - return $parent->name ?? false; - } - - /** - * Returns the bottom-most category/taxonomy value in a hierarchy given a - * post ID. - * - * (WordPress calls taxonomy values 'terms'). - * - * @since 3.3.0 Moved to class-metadata. - * - * @param int $post_id The post id you're interested in. - * @param string $taxonomy_name The name of the taxonomy. - * @return string Name of the custom taxonomy. - */ - private function get_bottom_level_term( int $post_id, string $taxonomy_name ): string { - $terms = get_the_terms( $post_id, $taxonomy_name ); - - if ( ! is_array( $terms ) ) { - return ''; - } - - $term_ids = wp_list_pluck( $terms, 'term_id' ); - $parents = array_filter( wp_list_pluck( $terms, 'parent' ) ); - - // Get array of IDs of terms which are not parents. - $term_ids_not_parents = array_diff( $term_ids, $parents ); - // Get corresponding term objects, which are mapped to array index keys. - $terms_not_parents = array_intersect_key( $terms, $term_ids_not_parents ); - // remove array index keys. - $terms_not_parents_cleaned = array_values( $terms_not_parents ); - - if ( isset( $terms_not_parents_cleaned[0] ) ) { - // If you assign multiple child terms in a custom taxonomy, will only return the first. - return $terms_not_parents_cleaned[0]->name; - } - - return ''; - } - - /** - * Retrieves all the authors for a post as an array. Can include multiple - * authors if the Co-Authors Plus plugin is in use. - * - * @since 3.3.0 Moved to class-metadata. - * - * @param WP_Post $post The post object. - * @return array - */ - public function get_author_names( WP_Post $post ): array { - $authors = $this->get_coauthor_names( $post->ID ); - if ( 0 === count( $authors ) ) { - $post_author = get_user_by( 'id', $post->post_author ); - if ( false !== $post_author ) { - $authors = array( $post_author ); - } - } - - /** - * Filters the list of author WP_User objects for a post. - * - * @since 1.14.0 - * - * @param array $authors One or more authors as WP_User objects. - * @param WP_Post $post Post object. - */ - $authors = apply_filters( 'wp_parsely_pre_authors', $authors, $post ); - - // Getting the author name for each author. - $authors = array_map( array( $this, 'get_author_name' ), $authors ); - - /** - * Filters the list of author names for a post. - * - * @since 1.14.0 - * - * @param array $authors One or more author names. - * @param WP_Post $post Post object. - */ - $authors = apply_filters( 'wp_parsely_post_authors', $authors, $post ); - - return array_map( array( $this, 'clean_value' ), $authors ); - } - - /** - * Returns a list of coauthors for a post assuming the Co-Authors Plus plugin - * is installed. - * - * Borrowed from - * https://github.com/Automattic/Co-Authors-Plus/blob/master/template-tags.php#L3-35 - * - * @since 3.3.0 Moved to class-metadata. - * - * @param int $post_id The ID of the post. - * @return array List of coauthors, or an empty array if the Co-Authors Plus plugin is not active. - */ - private function get_coauthor_names( int $post_id ): array { - $coauthors = array(); - if ( class_exists( 'coauthors_plus' ) ) { - global $post, $post_ID, $coauthors_plus; - - if ( $post_id <= 0 && $post_ID ) { - $post_id = $post_ID; - } - - if ( ! $post_id && $post ) { - $post_id = $post->ID; - } - - if ( $post_id ) { - $coauthor_terms = get_the_terms( $post_id, $coauthors_plus->coauthor_taxonomy ); - - if ( is_array( $coauthor_terms ) ) { - foreach ( $coauthor_terms as $coauthor ) { - $coauthor_slug = preg_replace( '#^cap-#', '', $coauthor->slug ); - $post_author = $coauthors_plus->get_coauthor_by( 'user_nicename', $coauthor_slug ); - // In case the user has been deleted while plugin was deactivated. - if ( false !== $post_author ) { - $coauthors[] = new WP_User( $post_author ); - } - } - } elseif ( ! $coauthors_plus->force_guest_authors ) { - if ( $post && $post_id === $post->ID ) { - $post_author = get_userdata( $post->post_author ); - } - if ( isset( $post_author ) && false !== $post_author ) { - $coauthors[] = $post_author; - } - } - // The empty else case is because if we force guest authors, we don't ever care what value wp_posts.post_author has. - } - } - return $coauthors; - } - - /** - * Determines author name from display name, falling back to firstname - * lastname, then nickname and finally the nicename. - * - * @since 3.3.0 Moved to class-metadata. - * - * @param ?WP_User $author The author of the post. - * @return string An author name. - */ - private function get_author_name( ?WP_User $author ): string { - // Gracefully handle situation where no author is available. - if ( null === $author ) { - return ''; - } - - if ( '' !== $author->display_name ) { - return $author->display_name; - } - - $author_name = $author->user_firstname . ' ' . $author->user_lastname; - if ( ' ' !== $author_name ) { - return $author_name; - } - - if ( '' !== $author->nickname ) { - return $author->nickname; - } - - if ( '' !== $author->user_nicename ) { - return $author->user_nicename; - } - - return ''; - } - - /** - * Returns the tags associated with this page or post. - * - * @since 3.3.0 Moved to class-metadata. - * - * @param int $post_id The ID of the post you're trying to get tags for. - * @return array The tags of the post represented by the post id. - */ - private function get_tags( int $post_id ): array { - /** - * Variable. - * - * @var array<\WP_Term|null>|\WP_Error - */ - $post_tags = wp_get_post_tags( $post_id ); - $tags = array(); - - if ( ! is_wp_error( $post_tags ) ) { - foreach ( $post_tags as $wp_tag ) { - if ( null !== $wp_tag ) { - $tags[] = $wp_tag->name; - } - } - } - - return $tags; - } - - /** - * Returns an array of all the child categories for the current post. - * - * @since 3.3.0 Moved to class-metadata. - * - * @param int $post_id The ID of the post you're trying to get categories for. - * @param string $delimiter What character will delimit the categories. - * @return array All the child categories of the current post. - */ - private function get_categories( int $post_id, string $delimiter = '/' ): array { - $tags = array(); - foreach ( get_the_category( $post_id ) as $category ) { - $hierarchy = get_category_parents( $category->term_id, false, $delimiter ); - if ( ! is_wp_error( $hierarchy ) ) { - $tags[] = rtrim( $hierarchy, '/' ); - } - } - // Take last element in the hierarchy, a string representing the full parent->child tree, - // and split it into individual category names. - $last_tag = end( $tags ); - if ( false !== $last_tag ) { - $tags = explode( '/', $last_tag ); - } - - // Remove default category name from tags if needed. - $default_category_name = get_cat_name( get_default_category() ); - return array_diff( $tags, array( $default_category_name ) ); - } - - /** - * Gets all term names from all custom taxonomies assigned to a post. - * - * @since 3.3.0 Moved to class-metadata. - * @since 3.4.0 Moved to class-post-builder. - * - * @param WP_Post $post_obj The post object to find the terms for. - * @return array Term names. - */ - private function get_custom_taxonomy_values( WP_Post $post_obj ): array { - // Filter out default WordPress taxonomies. - $all_taxonomies = array_diff( get_taxonomies(), array( 'post_tag', 'nav_menu', 'author', 'link_category', 'post_format' ) ); - - /** - * Filters the taxonomies. - * - * @since 3.11.0 - * - * @param array $all_taxonomies Taxonomies. - * @param WP_Post $post_obj Post object. - */ - $all_taxonomies = apply_filters( 'wp_parsely_custom_taxonomies', $all_taxonomies, $post_obj ); - $all_values = array(); - - foreach ( $all_taxonomies as $taxonomy ) { - $custom_taxonomy_objects = get_the_terms( $post_obj->ID, $taxonomy ); - if ( is_array( $custom_taxonomy_objects ) ) { - foreach ( $custom_taxonomy_objects as $custom_taxonomy_object ) { - $all_values[] = $custom_taxonomy_object->name; - } - } - } - - return $all_values; - } } diff --git a/src/RemoteAPI/class-analytics-post-detail-api.php b/src/RemoteAPI/class-analytics-post-detail-api.php index 444e7b9b9..a61049920 100644 --- a/src/RemoteAPI/class-analytics-post-detail-api.php +++ b/src/RemoteAPI/class-analytics-post-detail-api.php @@ -10,6 +10,7 @@ namespace Parsely\RemoteAPI; +use Parsely\Endpoints\Base_Endpoint; use Parsely\Parsely; /** @@ -23,10 +24,19 @@ class Analytics_Post_Detail_API extends Base_Endpoint_Remote { protected const QUERY_FILTER = 'wp_parsely_analytics_post_detail_endpoint_args'; /** - * Indicates whether the endpoint is public or protected behind permissions. + * Returns whether the endpoint is available for access by the current + * user. * - * @since 3.7.0 - * @var bool + * @since 3.14.0 + * + * @return bool */ - protected $is_public_endpoint = false; + public function is_available_to_current_user(): bool { + return current_user_can( + // phpcs:ignore WordPress.WP.Capabilities.Undetermined + $this->apply_capability_filters( + Base_Endpoint::DEFAULT_ACCESS_CAPABILITY + ) + ); + } } diff --git a/src/RemoteAPI/class-analytics-posts-api.php b/src/RemoteAPI/class-analytics-posts-api.php index a9e67af74..12d62d587 100644 --- a/src/RemoteAPI/class-analytics-posts-api.php +++ b/src/RemoteAPI/class-analytics-posts-api.php @@ -10,6 +10,7 @@ namespace Parsely\RemoteAPI; +use Parsely\Endpoints\Base_Endpoint; use Parsely\Parsely; use WP_Error; @@ -60,12 +61,21 @@ class Analytics_Posts_API extends Base_Endpoint_Remote { protected const QUERY_FILTER = 'wp_parsely_analytics_posts_endpoint_args'; /** - * Indicates whether the endpoint is public or protected behind permissions. + * Returns whether the endpoint is available for access by the current + * user. * - * @since 3.7.0 - * @var bool + * @since 3.14.0 + * + * @return bool */ - protected $is_public_endpoint = false; + public function is_available_to_current_user(): bool { + return current_user_can( + // phpcs:ignore WordPress.WP.Capabilities.Undetermined + $this->apply_capability_filters( + Base_Endpoint::DEFAULT_ACCESS_CAPABILITY + ) + ); + } /** * Calls Parse.ly Analytics API to get posts info. @@ -86,7 +96,7 @@ public function get_posts_analytics( $api_params ) { * * @return array The array of options. */ - protected function get_request_options(): array { + public function get_request_options(): array { return array( 'timeout' => 30, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout ); diff --git a/src/RemoteAPI/class-base-endpoint-remote.php b/src/RemoteAPI/class-base-endpoint-remote.php index 0d256328e..ba922562f 100644 --- a/src/RemoteAPI/class-base-endpoint-remote.php +++ b/src/RemoteAPI/class-base-endpoint-remote.php @@ -60,12 +60,7 @@ public function get_endpoint(): string { * @return string */ public function get_api_url( array $query ): string { - if ( static::ENDPOINT === '' ) { - throw new UnexpectedValueException( 'ENDPOINT constant must be defined in child class.' ); - } - if ( static::QUERY_FILTER === '' ) { - throw new UnexpectedValueException( 'QUERY_FILTER constant must be defined in child class.' ); - } + $this->validate_required_constraints(); $query['apikey'] = $this->parsely->get_site_id(); if ( $this->parsely->api_secret_is_set() ) { @@ -133,7 +128,23 @@ public function get_items( array $query, bool $associative = false ) { * * @return array The array of options. */ - protected function get_request_options(): array { + public function get_request_options(): array { return array(); } + + /** + * Validates that required constants are defined. + * + * @since 3.14.0 + * + * @throws UnexpectedValueException If any required constant is not defined. + */ + protected function validate_required_constraints(): void { + if ( static::ENDPOINT === '' ) { + throw new UnexpectedValueException( 'ENDPOINT constant must be defined in child class.' ); + } + if ( static::QUERY_FILTER === '' ) { + throw new UnexpectedValueException( 'QUERY_FILTER constant must be defined in child class.' ); + } + } } diff --git a/src/RemoteAPI/class-referrers-post-detail-api.php b/src/RemoteAPI/class-referrers-post-detail-api.php index bf9c3f2f9..51e91c9ec 100644 --- a/src/RemoteAPI/class-referrers-post-detail-api.php +++ b/src/RemoteAPI/class-referrers-post-detail-api.php @@ -10,6 +10,7 @@ namespace Parsely\RemoteAPI; +use Parsely\Endpoints\Base_Endpoint; use Parsely\Parsely; /** @@ -23,10 +24,19 @@ class Referrers_Post_Detail_API extends Base_Endpoint_Remote { protected const QUERY_FILTER = 'wp_parsely_referrers_post_detail_endpoint_args'; /** - * Indicates whether the endpoint is public or protected behind permissions. + * Returns whether the endpoint is available for access by the current + * user. * - * @since 3.7.0 - * @var bool + * @since 3.14.0 + * + * @return bool */ - protected $is_public_endpoint = false; + public function is_available_to_current_user(): bool { + return current_user_can( + // phpcs:ignore WordPress.WP.Capabilities.Undetermined + $this->apply_capability_filters( + Base_Endpoint::DEFAULT_ACCESS_CAPABILITY + ) + ); + } } diff --git a/src/RemoteAPI/class-related-api.php b/src/RemoteAPI/class-related-api.php index 096e0c4e3..93b47bba2 100644 --- a/src/RemoteAPI/class-related-api.php +++ b/src/RemoteAPI/class-related-api.php @@ -23,10 +23,14 @@ class Related_API extends Base_Endpoint_Remote { protected const QUERY_FILTER = 'wp_parsely_related_endpoint_args'; /** - * Indicates whether the endpoint is public or protected behind permissions. + * Returns whether the endpoint is available for access by the current + * user. * - * @since 3.7.0 - * @var bool + * @since 3.14.0 + * + * @return bool */ - protected $is_public_endpoint = true; + public function is_available_to_current_user(): bool { + return true; + } } diff --git a/src/RemoteAPI/class-remote-api-cache.php b/src/RemoteAPI/class-remote-api-cache.php index 072219bd7..a37e67006 100644 --- a/src/RemoteAPI/class-remote-api-cache.php +++ b/src/RemoteAPI/class-remote-api-cache.php @@ -74,13 +74,15 @@ public function get_items( array $query, bool $associative = false ) { } /** - * Checks if the current user is allowed to make the API call. + * Returns whether the endpoint is available for access by the current + * user. * * @since 3.7.0 + * @since 3.14.0 Renamed from `is_user_allowed_to_make_api_call()`. * * @return bool */ - public function is_user_allowed_to_make_api_call(): bool { - return $this->remote_api->is_user_allowed_to_make_api_call(); + public function is_available_to_current_user(): bool { + return $this->remote_api->is_available_to_current_user(); } } diff --git a/src/RemoteAPI/class-validate-api.php b/src/RemoteAPI/class-validate-api.php index d880ca96c..e7941cff3 100644 --- a/src/RemoteAPI/class-validate-api.php +++ b/src/RemoteAPI/class-validate-api.php @@ -10,6 +10,7 @@ namespace Parsely\RemoteAPI; +use Parsely\Endpoints\Base_Endpoint; use Parsely\Parsely; use WP_Error; @@ -28,13 +29,21 @@ class Validate_API extends Base_Endpoint_Remote { protected const QUERY_FILTER = 'wp_parsely_validate_secret_endpoint_args'; /** - * Indicates whether the endpoint is public or protected behind permissions. + * Returns whether the endpoint is available for access by the current + * user. * - * @since 3.11.0 + * @since 3.14.0 * - * @var bool + * @return bool */ - protected $is_public_endpoint = false; + public function is_available_to_current_user(): bool { + return current_user_can( + // phpcs:ignore WordPress.WP.Capabilities.Undetermined + $this->apply_capability_filters( + Base_Endpoint::DEFAULT_ACCESS_CAPABILITY + ) + ); + } /** * Gets the URL for the Parse.ly API credentials validation endpoint. diff --git a/src/RemoteAPI/content-suggestions/class-content-suggestions-base-api.php b/src/RemoteAPI/content-suggestions/class-content-suggestions-base-api.php index 5bb5cc1ea..d38de3a48 100644 --- a/src/RemoteAPI/content-suggestions/class-content-suggestions-base-api.php +++ b/src/RemoteAPI/content-suggestions/class-content-suggestions-base-api.php @@ -10,8 +10,10 @@ namespace Parsely\RemoteAPI\ContentSuggestions; +use Parsely\Endpoints\Base_Endpoint; use Parsely\Parsely; use Parsely\RemoteAPI\Base_Endpoint_Remote; +use UnexpectedValueException; use WP_Error; /** @@ -21,9 +23,26 @@ * * @phpstan-import-type WP_HTTP_Request_Args from Parsely */ -class Content_Suggestions_Base_API extends Base_Endpoint_Remote { +abstract class Content_Suggestions_Base_API extends Base_Endpoint_Remote { protected const API_BASE_URL = Parsely::PUBLIC_SUGGESTIONS_API_BASE_URL; + /** + * Returns whether the endpoint is available for access by the current + * user. + * + * @since 3.14.0 + * + * @return bool + */ + public function is_available_to_current_user(): bool { + return current_user_can( + // phpcs:ignore WordPress.WP.Capabilities.Undetermined + $this->apply_capability_filters( + Base_Endpoint::DEFAULT_ACCESS_CAPABILITY + ) + ); + } + /** * Returns the request's options for the remote API call. * @@ -31,28 +50,62 @@ class Content_Suggestions_Base_API extends Base_Endpoint_Remote { * * @return array The array of options. */ - protected function get_request_options(): array { - return array( + public function get_request_options(): array { + $options = array( 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ), 'data_format' => 'body', 'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout 'body' => '{}', ); + + // Add API key to request headers. + if ( $this->parsely->api_secret_is_set() ) { + $options['headers']['X-APIKEY-SECRET'] = $this->parsely->get_api_secret(); + } + + return $options; + } + + /** + * Gets the URL for a particular Parse.ly API Content Suggestion endpoint. + * + * @since 3.14.0 + * + * @param array $query The query arguments to send to the remote API. + * @throws UnexpectedValueException If the endpoint constant is not defined. + * @throws UnexpectedValueException If the query filter constant is not defined. + * @return string + */ + public function get_api_url( array $query = array() ): string { + $this->validate_required_constraints(); + + $query['apikey'] = $this->parsely->get_site_id(); + + // Remove empty entries and sort by key so the query args are in + // alphabetical order. + $query = array_filter( $query ); + ksort( $query ); + + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Hook names are defined in child classes. + $query = apply_filters( static::QUERY_FILTER, $query ); + return add_query_arg( $query, static::API_BASE_URL . static::ENDPOINT ); } /** * Sends a POST request to the Parse.ly Content Suggestion API. * - * This method sends a POST request to the Parse.ly Content Suggestion API and returns the response. - * The response is either a WP_Error object in case of an error, or a decoded JSON object in case of a - * successful request. + * This method sends a POST request to the Parse.ly Content Suggestion API and returns the + * response. The response is either a WP_Error object in case of an error, or a decoded JSON + * object in case of a successful request. * * @since 3.13.0 * - * @param array $query An associative array containing the query parameters for the API request. - * @param array $body An associative array containing the body parameters for the API request. - * @return WP_Error|object Returns a WP_Error object in case of an error, or a decoded JSON object - * case of a successful request. + * @param array $query An associative array containing the query + * parameters for the API request. + * @param array> $body An associative array containing the body + * parameters for the API request. + * @return WP_Error|object Returns a WP_Error object in case of an error, or a decoded JSON + * object in case of a successful request. */ protected function post_request( array $query = array(), array $body = array() ) { $full_api_url = $this->get_api_url( $query ); diff --git a/src/RemoteAPI/content-suggestions/class-link-suggestion.php b/src/RemoteAPI/content-suggestions/class-link-suggestion.php new file mode 100644 index 000000000..1caa97d5a --- /dev/null +++ b/src/RemoteAPI/content-suggestions/class-link-suggestion.php @@ -0,0 +1,48 @@ + array( + 'persona' => $persona, + 'style' => $style, + ), + 'title' => $title, + 'text' => $content, + ); + + $decoded = $this->post_request( array(), $body ); + + if ( is_wp_error( $decoded ) ) { + return $decoded; + } + + if ( ! property_exists( $decoded, 'result' ) || + ! is_string( $decoded->result[0] ) ) { + return new WP_Error( + 400, + __( 'Unable to parse meta description from upstream API', 'wp-parsely' ) + ); + } + + return $decoded->result[0]; + } +} diff --git a/src/RemoteAPI/content-suggestions/class-suggest-headline-api.php b/src/RemoteAPI/content-suggestions/class-suggest-headline-api.php new file mode 100644 index 000000000..48413fe7c --- /dev/null +++ b/src/RemoteAPI/content-suggestions/class-suggest-headline-api.php @@ -0,0 +1,69 @@ +|WP_Error The response from the remote API, or a WP_Error + * object if the response is an error. + */ + public function get_titles( + string $content, + int $limit, + string $persona = 'journalist', + string $tone = 'neutral' + ) { + $body = array( + 'output_config' => array( + 'persona' => $persona, + 'style' => $tone, + 'max_items' => $limit, + ), + 'text' => $content, + ); + + $decoded = $this->post_request( array(), $body ); + + if ( is_wp_error( $decoded ) ) { + return $decoded; + } + + if ( ! property_exists( $decoded, 'result' ) || ! is_array( $decoded->result ) ) { + return new WP_Error( + 400, + __( 'Unable to parse titles from upstream API', 'wp-parsely' ) + ); + } + + return $decoded->result; + } +} diff --git a/src/RemoteAPI/content-suggestions/class-suggest-linked-reference-api.php b/src/RemoteAPI/content-suggestions/class-suggest-linked-reference-api.php new file mode 100644 index 000000000..91f4159cf --- /dev/null +++ b/src/RemoteAPI/content-suggestions/class-suggest-linked-reference-api.php @@ -0,0 +1,81 @@ + array( + 'max_link_words' => $max_link_words, + 'max_items' => $max_links, + ), + 'text' => $content, + ); + + $decoded = $this->post_request( array(), $body ); + + if ( is_wp_error( $decoded ) ) { + return $decoded; + } + + if ( ! property_exists( $decoded, 'result' ) || + ! is_array( $decoded->result ) ) { + return new WP_Error( + 400, + __( 'Unable to parse suggested links from upstream API', 'wp-parsely' ) + ); + } + + // Convert the links to Link_Suggestion objects. + $links = array(); + foreach ( $decoded->result as $link ) { + $link_obj = new Link_Suggestion(); + $link_obj->href = $link->canonical_url; + $link_obj->title = $link->title; + $link_obj->text = $link->text; + $link_obj->offset = $link->offset; + $links[] = $link_obj; + } + + return $links; + } +} diff --git a/src/RemoteAPI/content-suggestions/class-suggest-meta-description-api.php b/src/RemoteAPI/content-suggestions/class-suggest-meta-description-api.php deleted file mode 100644 index a411c06ea..000000000 --- a/src/RemoteAPI/content-suggestions/class-suggest-meta-description-api.php +++ /dev/null @@ -1,64 +0,0 @@ - $title, - ); - - $decoded = $this->post_request( $query, array( 'content' => $content ) ); - - if ( is_wp_error( $decoded ) ) { - return $decoded; - } - - if ( ! property_exists( $decoded, 'meta_description' ) || - ! is_string( $decoded->meta_description ) ) { - return new WP_Error( 400, __( 'Unable to parse meta description from upstream API', 'wp-parsely' ) ); - } - - return $decoded->meta_description; - } -} diff --git a/src/RemoteAPI/content-suggestions/class-write-title-api.php b/src/RemoteAPI/content-suggestions/class-write-title-api.php deleted file mode 100644 index 2b088a719..000000000 --- a/src/RemoteAPI/content-suggestions/class-write-title-api.php +++ /dev/null @@ -1,65 +0,0 @@ -|WP_Error The response from the remote API, or a WP_Error - * object if the response is an error. - */ - public function get_titles( string $content, int $limit, string $persona = 'journalist', string $tone = 'neutral' ) { - $query = array( - 'persona' => $persona, - 'style' => $tone, - 'limit' => $limit, - ); - - $decoded = $this->post_request( $query, array( 'text' => $content ) ); - - if ( is_wp_error( $decoded ) ) { - return $decoded; - } - - if ( ! property_exists( $decoded, 'titles' ) || ! is_array( $decoded->titles ) ) { - return new WP_Error( 400, __( 'Unable to parse titles from upstream API', 'wp-parsely' ) ); - } - - return $decoded->titles; - } -} diff --git a/src/RemoteAPI/interface-remote-api.php b/src/RemoteAPI/interface-remote-api.php index 3496390e1..45d42c4e6 100644 --- a/src/RemoteAPI/interface-remote-api.php +++ b/src/RemoteAPI/interface-remote-api.php @@ -27,9 +27,12 @@ interface Remote_API_Interface { public function get_items( array $query, bool $associative = false ); /** - * Checks if the current user is allowed to make the API call. + * Returns whether the endpoint is available for access by the current + * user. + * + * @since 3.14.0 Renamed from `is_user_allowed_to_make_api_call()`. * * @return bool */ - public function is_user_allowed_to_make_api_call(): bool; + public function is_available_to_current_user(): bool; } diff --git a/src/UI/class-settings-page.php b/src/UI/class-settings-page.php index 226ccf1ac..f274b8b19 100644 --- a/src/UI/class-settings-page.php +++ b/src/UI/class-settings-page.php @@ -55,6 +55,7 @@ * track_post_types_as?: array, * track_post_types: string[], * track_page_types: string[], + * full_metadata_in_non_posts: ?bool, * content_id_prefix?: string, * use_top_level_cats?:bool|string, * custom_taxonomy_section?: string, @@ -431,6 +432,25 @@ function (): void { ) ); + // Use full metadata in non-posts. + $field_id = 'full_metadata_in_non_posts'; + add_settings_field( + $field_id, + $this->set_field_label_contents( __( 'Use Full Metadata in Non-Posts', 'wp-parsely' ), $field_id ), + array( $this, 'print_radio_tags' ), + Parsely::MENU_SLUG, + $section_key, + array( + 'title' => __( 'Use Full Metadata in Non-Posts', 'wp-parsely' ), // Passed for legend element. + 'option_key' => $field_id, + 'radio_options' => array( + 'true' => __( 'Yes, add full metadata to Post Types being tracked as Non-Posts.', 'wp-parsely' ), + 'false' => __( 'No, we have code that modifies metadata and that has not been tested for compatibility yet.', 'wp-parsely' ), + ), + 'help_text' => __( 'Important: This setting will be removed in the future, force-enabling this behavior. If you\'re using any code that modifies metadata in a way that conflicts with this setting when it is enabled, please apply any needed fixes.', 'wp-parsely' ), + ) + ); + // Content ID Prefix. $field_id = 'content_id_prefix'; $field_args = array( @@ -1073,6 +1093,14 @@ private function validate_recrawl_section( $input ) { $input['content_id_prefix'] = sanitize_text_field( $input['content_id_prefix'] ); } + // Full metadata in non-posts. + if ( ! isset( $input['full_metadata_in_non_posts'] ) ) { + $input['full_metadata_in_non_posts'] = true; + } else { + // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual + $input['full_metadata_in_non_posts'] = 'true' == $input['full_metadata_in_non_posts']; + } + // Allow for Top-level categories setting to be conditionally included on the page. // If it's not shown, then set the value as what was previously saved. if ( ! isset( $input['use_top_level_cats'] ) ) { diff --git a/src/class-metadata.php b/src/class-metadata.php index 8afac0812..aa01c22b6 100644 --- a/src/class-metadata.php +++ b/src/class-metadata.php @@ -87,7 +87,7 @@ public function __construct( Parsely $parsely ) { * @param WP_Post $post object. * @return Metadata_Attributes */ - public function construct_metadata( WP_Post $post ) { + public function construct_metadata( WP_Post $post ): array { $options = $this->parsely->get_options(); $queried_object_id = get_queried_object_id(); @@ -121,9 +121,9 @@ public function construct_metadata( WP_Post $post ) { } if ( isset( $builder ) ) { - $parsely_page = $builder->get_metadata(); + $current_metadata = $builder->get_metadata(); } else { - $parsely_page = array(); + $current_metadata = array(); } /** @@ -132,11 +132,11 @@ public function construct_metadata( WP_Post $post ) { * @since 2.5.0 * @var mixed * - * @param array $parsely_page Existing structured metadata for a page. + * @param array $current_metadata The currently generated metadata. * @param WP_Post $post Post object. * @param array $options The Parse.ly options. */ - $filtered = apply_filters( 'wp_parsely_metadata', $parsely_page, $post, $options ); + $filtered = apply_filters( 'wp_parsely_metadata', $current_metadata, $post, $options ); if ( is_array( $filtered ) ) { return $filtered; } diff --git a/src/class-parsely.php b/src/class-parsely.php index dd0f9fc82..82b6cf0b4 100644 --- a/src/class-parsely.php +++ b/src/class-parsely.php @@ -33,6 +33,7 @@ * track_post_types: string[], * track_page_types: string[], * track_post_types_as?: array, + * full_metadata_in_non_posts: ?bool, * disable_javascript: bool, * disable_amp: bool, * meta_type: string, @@ -63,7 +64,7 @@ class Parsely { public const CAPABILITY = 'manage_options'; // The capability required to administer settings. public const DASHBOARD_BASE_URL = 'https://dash.parsely.com'; public const PUBLIC_API_BASE_URL = 'https://api.parsely.com/v2'; - public const PUBLIC_SUGGESTIONS_API_BASE_URL = 'https://content-suggestions-beta.parsely-recspod.net'; + public const PUBLIC_SUGGESTIONS_API_BASE_URL = 'https://content-suggestions-api.parsely.net/prod'; /** * Declare some class properties @@ -71,24 +72,25 @@ class Parsely { * @var Parsely_Options $option_defaults The defaults we need for the class. */ private $option_defaults = array( - 'apikey' => '', - 'content_id_prefix' => '', - 'api_secret' => '', - 'use_top_level_cats' => false, - 'custom_taxonomy_section' => 'category', - 'cats_as_tags' => false, - 'track_authenticated_users' => false, - 'lowercase_tags' => true, - 'force_https_canonicals' => false, - 'track_post_types' => array(), - 'track_page_types' => array(), - 'disable_javascript' => false, - 'disable_amp' => false, - 'meta_type' => 'json_ld', - 'logo' => '', - 'metadata_secret' => '', - 'disable_autotrack' => false, - 'plugin_version' => self::VERSION, + 'apikey' => '', + 'content_id_prefix' => '', + 'api_secret' => '', + 'use_top_level_cats' => false, + 'custom_taxonomy_section' => 'category', + 'cats_as_tags' => false, + 'track_authenticated_users' => false, + 'lowercase_tags' => true, + 'force_https_canonicals' => false, + 'track_post_types' => array(), + 'track_page_types' => array(), + 'full_metadata_in_non_posts' => true, + 'disable_javascript' => false, + 'disable_amp' => false, + 'meta_type' => 'json_ld', + 'logo' => '', + 'metadata_secret' => '', + 'disable_autotrack' => false, + 'plugin_version' => self::VERSION, ); /** @@ -403,8 +405,13 @@ public function get_options() { */ $options = get_option( self::OPTIONS_KEY, null ); + if ( is_array( $options ) && ! isset( $options['full_metadata_in_non_posts'] ) ) { + $this->set_default_full_metadata_in_non_posts(); + } + if ( ! is_array( $options ) ) { $this->set_default_track_as_values(); + $this->set_default_full_metadata_in_non_posts(); $options = $this->option_defaults; } @@ -446,6 +453,35 @@ public function set_default_track_as_values(): void { } } + /** + * Sets the default value for the full_metadata_in_non_posts option. + * + * @since 3.14.0 + */ + public function set_default_full_metadata_in_non_posts(): void { + $this->option_defaults['full_metadata_in_non_posts'] = true; + + // Usage of any of these filters will result in the setting being set + // to false. + $filter_tags = array( + 'wp_parsely_metadata', + 'wp_parsely_post_tags', + 'wp_parsely_permalink', + 'wp_parsely_post_category', + 'wp_parsely_pre_authors', + 'wp_parsely_post_authors', + 'wp_parsely_custom_taxonomies', + 'wp_parsely_post_type', + ); + + foreach ( $filter_tags as $filter_tag ) { + if ( has_filter( $filter_tag ) ) { + $this->option_defaults['full_metadata_in_non_posts'] = false; + break; + } + } + } + /** * Gets the URL of the plugin's settings page. * diff --git a/src/content-helper/common/components/input-range/component.tsx b/src/content-helper/common/components/input-range/component.tsx new file mode 100644 index 000000000..693d0c467 --- /dev/null +++ b/src/content-helper/common/components/input-range/component.tsx @@ -0,0 +1,83 @@ +/** + * WordPress dependencies + */ +import { + __experimentalHeading as Heading, + __experimentalNumberControl as NumberControl, + __experimentalInputControlSuffixWrapper as InputControlSuffixWrapper, + RangeControl, +} from '@wordpress/components'; + +/** + * Defines the props structure for InputRange. + * + * @since 3.14.0 + */ +type InputRangeProps = { + value: number; + onChange: ( value: number | undefined ) => void; + max: number; + min: number; + suffix: string; + label: string; + initialPosition: number; + disabled: boolean; + className?: string; + size?: 'small' | 'default' | 'compact' | '__unstable-large', +}; + +/** + * Component that renders a hybrid input range control. + * On one side you have the number control and on the other side the range control. + * + * @since 3.14.0 + * + * @param {InputRangeProps} props The component's props. + */ +export const InputRange = ( { + value, + onChange, + max, + min, + suffix, + size, + label, + initialPosition, + disabled, + className, +}: Readonly ): JSX.Element => { + return ( +
+ { label } +
+ { suffix } } + size={ size ?? '__unstable-large' } + min={ min } + max={ max } + onChange={ ( newValue ) => { + const numericValue = parseInt( newValue as string, 10 ); + if ( isNaN( numericValue ) ) { + return; + } + onChange( numericValue ); + } } + /> + { + onChange( newValue ); + } } + withInputField={ false } + min={ min } + max={ max } + /> +
+
+ ); +}; diff --git a/src/content-helper/common/components/input-range/index.ts b/src/content-helper/common/components/input-range/index.ts new file mode 100644 index 000000000..d2d359a01 --- /dev/null +++ b/src/content-helper/common/components/input-range/index.ts @@ -0,0 +1 @@ +export { InputRange } from './component'; diff --git a/src/content-helper/common/components/input-range/style.scss b/src/content-helper/common/components/input-range/style.scss new file mode 100644 index 000000000..7dd7a760a --- /dev/null +++ b/src/content-helper/common/components/input-range/style.scss @@ -0,0 +1,58 @@ +@import "../../css/functions"; +@import "../../css/variables"; + +.parsely-inputrange-control { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--grid-unit-10, to_rem(8px)); + align-self: stretch; + + .parsely-inputrange-control__label { + margin: 0; + } + + .parsely-inputrange-control__controls { + display: flex; + height: to_rem(40px); + align-items: center; + gap: var(--grid-unit-20, to_rem(16px)); + align-self: stretch; + + .components-input-control { + display: flex; + flex: 1 0 0; + + /* For Chrome, Safari, Edge */ + input[type="number"]::-webkit-inner-spin-button, + input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } + + /* For Firefox */ + input[type="number"] { + -moz-appearance: textfield; + } + + .components-base-control__field { + flex-grow: 1; + } + + .components-input-control__suffix { + color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)); + } + } + + .components-range-control { + height: 36px; + flex: 1 0 0; + + .components-range-control__root { + height: to_rem(40px); + } + } + } + + +} diff --git a/src/content-helper/common/components/persona-selector/component.tsx b/src/content-helper/common/components/persona-selector/component.tsx index 60fd4a48e..08dbc2820 100644 --- a/src/content-helper/common/components/persona-selector/component.tsx +++ b/src/content-helper/common/components/persona-selector/component.tsx @@ -8,65 +8,66 @@ import { MenuItem, TextControl, } from '@wordpress/components'; +import { useDebounce } from '@wordpress/compose'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Icon, chevronRight } from '@wordpress/icons'; +import { Icon, edit } from '@wordpress/icons'; /** * Internal dependencies */ -import { PersonIcon } from '../../icons/person-icon'; +import { MoreArrow } from '../../icons/more-arrow'; + +/** + * Represents a single persona in the PARSELY_PERSONAS list. + * + * @since 3.14.0 + */ +type PersonaMetadata = { + label: string, + icon?: JSX.Element, +}; /** * List of the available personas. - * Each persona has a label and an emoji. + * Each persona has a label and an optional icon. * * @since 3.13.0 */ -export const PARSELY_PERSONAS = { +export const PARSELY_PERSONAS: Record = { journalist: { label: __( 'Journalist', 'wp-parsely' ), - emoji: '📰', }, editorialWriter: { label: __( 'Editorial Writer', 'wp-parsely' ), - emoji: '✍️', }, investigativeReporter: { label: __( 'Investigative Reporter', 'wp-parsely' ), - emoji: '🕵️', }, techAnalyst: { label: __( 'Tech Analyst', 'wp-parsely' ), - emoji: '💻', }, businessAnalyst: { label: __( 'Business Analyst', 'wp-parsely' ), - emoji: '📈', }, culturalCommentator: { label: __( 'Cultural Commentator', 'wp-parsely' ), - emoji: '🌍', }, scienceCorrespondent: { label: __( 'Science Correspondent', 'wp-parsely' ), - emoji: '🔬', }, politicalAnalyst: { label: __( 'Political Analyst', 'wp-parsely' ), - emoji: '🏛️', }, healthWellnessAdvocate: { label: __( 'Health and Wellness Advocate', 'wp-parsely' ), - emoji: '🍏', }, environmentalJournalist: { label: __( 'Environmental Journalist', 'wp-parsely' ), - emoji: '🌳', }, custom: { - label: __( 'Use a custom persona', 'wp-parsely' ), - emoji: '🔧', + label: __( 'Custom Persona', 'wp-parsely' ), + icon: edit, }, }; @@ -76,13 +77,13 @@ type FixedPersonaProp = keyof typeof PARSELY_PERSONAS; const PERSONAS_LIST = Object.keys( PARSELY_PERSONAS ) as PersonaProp[]; /** - * Returns the label for a given persona. + * Returns the value for a given persona. * * @since 3.13.0 * * @param {PersonaProp} persona The persona to get the label for. * - * @return {string} The label for the given persona. + * @return {string} The value for the given persona. */ export const getLabel = ( persona: PersonaProp ): string => { if ( persona === 'custom' || persona === '' ) { @@ -132,15 +133,27 @@ const CustomPersona = ( { value, onChange }: Readonly ): JSX.Element => { const [ customPersona, setCustomPersona ] = useState( '' ); + const debouncedOnChange = useDebounce( onChange, 500 ); + return (
{ - onChange( newPersona ); + // If the persona is empty, set it to an empty string, and avoid debouncing. + if ( '' === newPersona ) { + onChange( '' ); + setCustomPersona( '' ); + return; + } + // Truncate the persona to 32 characters. + if ( newPersona.length > 32 ) { + newPersona = newPersona.slice( 0, 32 ); + } + debouncedOnChange( newPersona ); setCustomPersona( newPersona ); } } - help={ __( 'Enter a custom persona', 'wp-parsely' ) } />
); @@ -156,6 +169,7 @@ type PersonaSelectorProps = { onChange: ( persona: PersonaProp ) => void; onDropdownChange?: ( persona: PersonaProp ) => void; disabled?: boolean; + value?: string; label?: string; allowCustom?: boolean; }; @@ -171,7 +185,8 @@ type PersonaSelectorProps = { */ export const PersonaSelector = ( { persona, - label = __( 'Select a persona', 'wp-parsely' ), + value = __( 'Select a persona…', 'wp-parsely' ), + label = __( 'Persona', 'wp-parsely' ), onChange, onDropdownChange, disabled = false, @@ -179,9 +194,9 @@ export const PersonaSelector = ( { }: Readonly ): JSX.Element => { return ( + { label &&
{ label }
}
- { label } + { isCustomPersona( persona ) ? PARSELY_PERSONAS.custom.label : value }
- + ), } } > { ( { onClose } ) => ( - + <> { PERSONAS_LIST.map( ( singlePersona ) => { if ( ! allowCustom && singlePersona === 'custom' ) { @@ -206,19 +221,31 @@ export const PersonaSelector = ( { } const personaData = PARSELY_PERSONAS[ singlePersona as FixedPersonaProp ]; + const isSelected = singlePersona === persona || ( isCustomPersona( persona ) && singlePersona === 'custom' ); return ( { onDropdownChange?.( singlePersona as FixedPersonaProp ); onChange( singlePersona ); onClose(); + // Focus the input when the custom persona is selected. + if ( singlePersona === 'custom' ) { + // Wait for the input to be rendered. + setTimeout( () => { + const inputElement = document.querySelector( `.parsely-persona-selector-custom input` ) as HTMLInputElement; + if ( inputElement ) { + inputElement.focus(); + } + }, 0 ); + } } } > - { personaData.emoji } { personaData.label } + { personaData.icon && } + { personaData.label } ); } ) } diff --git a/src/content-helper/common/components/persona-selector/style.scss b/src/content-helper/common/components/persona-selector/style.scss index 9ac2e4dec..de68ebb7d 100644 --- a/src/content-helper/common/components/persona-selector/style.scss +++ b/src/content-helper/common/components/persona-selector/style.scss @@ -3,7 +3,23 @@ .parsely-persona-selector-dropdown { width: 100%; - margin-bottom: to_rem(10px); + overflow-wrap: break-word; + word-break: break-word; + height: to_rem(36px); + align-items: center; + gap: var(--grid-unit-05, to_rem(4px)); + align-self: stretch; + border-radius: 2px; + border: 1px solid var(--Gutenberg-Gray-600, #949494); + background: var(--Gutenberg-White, #fff); + + .components-dropdown-menu__toggle.has-icon svg:nth-child(1) { + display: none; + } + + button { + height: to_rem(33px); + } &.is-disabled { opacity: 0.5; @@ -19,6 +35,7 @@ .parsely-persona-selector-label { flex-grow: 2; text-align: left; + padding: 0 var(--grid-unit-10, to_rem(8px)); &::first-letter { text-transform: uppercase; @@ -26,16 +43,18 @@ } } -.wp-parsely-popover { - - button.components-button.components-menu-item__button.is-selected { - outline: 3px solid transparent; - box-shadow: 0 0 0 2px var(--parsely-green); +.parsely-tone-selector-custom { + width: 100%; - &:focus { - outline: 3px solid transparent; - box-shadow: 0 0 0 2px var(--parsely-green); - } + .components-base-control__field input { + display: flex; + height: to_rem(40px); + padding: var(--grid-unit-15, to_rem(12px)) var(--grid-unit-20, to_rem(16px)); + align-items: center; + gap: var(--grid-unit-05, to_rem(4px)); + align-self: stretch; + border-radius: 2px; + border: 1px solid var(--Gutenberg-Gray-600, #949494); } } diff --git a/src/content-helper/common/components/tone-selector/component.tsx b/src/content-helper/common/components/tone-selector/component.tsx index 07fed704d..fe3ad5ffc 100644 --- a/src/content-helper/common/components/tone-selector/component.tsx +++ b/src/content-helper/common/components/tone-selector/component.tsx @@ -8,60 +8,66 @@ import { MenuItem, TextControl, } from '@wordpress/components'; +import { useDebounce } from '@wordpress/compose'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Icon, chevronRight, megaphone } from '@wordpress/icons'; +import { Icon, edit } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { MoreArrow } from '../../icons/more-arrow'; + +/** + * Represents a single tone in the PARSELY_TONES list. + * + * @since 3.14.0 + */ +type ToneMetadata = { + label: string, + icon?: JSX.Element, +}; /** * List of the available tones. - * Each tone has a label and an emoji. + * Each tone has a label and an optional icon. * * @since 3.13.0 */ -export const PARSELY_TONES = { +export const PARSELY_TONES: Record = { neutral: { label: __( 'Neutral', 'wp-parsely' ), - emoji: '😐', }, formal: { label: __( 'Formal', 'wp-parsely' ), - emoji: '🎩', }, humorous: { label: __( 'Humorous', 'wp-parsely' ), - emoji: '😂', }, confident: { label: __( 'Confident', 'wp-parsely' ), - emoji: '😎', }, provocative: { label: __( 'Provocative', 'wp-parsely' ), - emoji: '😈', }, serious: { label: __( 'Serious', 'wp-parsely' ), - emoji: '🧐', }, inspirational: { label: __( 'Inspirational', 'wp-parsely' ), - emoji: '✨', }, skeptical: { label: __( 'Skeptical', 'wp-parsely' ), - emoji: '🤨', }, conversational: { label: __( 'Conversational', 'wp-parsely' ), - emoji: '💬', }, analytical: { label: __( 'Analytical', 'wp-parsely' ), - emoji: '🤓', }, custom: { - label: __( 'Use a custom tone', 'wp-parsely' ), - emoji: '🔧', + label: __( 'Custom Tone', 'wp-parsely' ), + icon: edit, }, }; @@ -71,13 +77,13 @@ type FixedToneProp = keyof typeof PARSELY_TONES; const TONE_LIST = Object.keys( PARSELY_TONES ) as ToneProp[]; /** - * Returns the label for a given tone. + * Returns the value for a given tone. * * @since 3.13.0 * * @param {ToneProp} tone The tone to get the label for. * - * @return {string} The label for the given tone. + * @return {string} The value for the given tone. */ export const getLabel = ( tone: ToneProp ): string => { if ( tone === 'custom' || tone === '' ) { @@ -127,15 +133,27 @@ const CustomTone = ( { value, onChange }: Readonly ): JSX.Element => { const [ customTone, setCustomTone ] = useState( '' ); + const debouncedOnChange = useDebounce( onChange, 500 ); + return (
{ - onChange( newTone ); + // If the tone is empty, set it to an empty string, and avoid debouncing. + if ( '' === newTone ) { + onChange( '' ); + setCustomTone( '' ); + return; + } + // Truncate the tone to 32 characters. + if ( newTone.length > 32 ) { + newTone = newTone.slice( 0, 32 ); + } + debouncedOnChange( newTone ); setCustomTone( newTone ); } } - help={ __( 'Enter a custom tone', 'wp-parsely' ) } />
); @@ -151,6 +169,7 @@ type ToneSelectorProps = { onChange: ( tone: ToneProp | string ) => void; onDropdownChange?: ( tone: ToneProp ) => void; disabled?: boolean; + value?: string; label?: string; allowCustom?: boolean; }; @@ -166,7 +185,8 @@ type ToneSelectorProps = { */ export const ToneSelector = ( { tone, - label = __( 'Select a tone', 'wp-parsely' ), + value = __( 'Select a tone', 'wp-parsely' ), + label = __( 'Tone', 'wp-parsely' ), onChange, onDropdownChange, disabled = false, @@ -174,9 +194,9 @@ export const ToneSelector = ( { }: Readonly ): JSX.Element => { return ( +
{ label }
- { label } + { isCustomTone( tone ) ? PARSELY_TONES.custom.label : value }
- + ), } } @@ -201,20 +221,31 @@ export const ToneSelector = ( { } const toneData = PARSELY_TONES[ singleTone as FixedToneProp ]; - + const isSelected = singleTone === tone || ( isCustomTone( tone ) && singleTone === 'custom' ); return ( { onDropdownChange?.( singleTone as FixedToneProp ); onChange( singleTone ); onClose(); + // Focus the input when the custom tone is selected. + if ( singleTone === 'custom' ) { + // Wait for the input to be rendered. + setTimeout( () => { + const inputElement = document.querySelector( `.parsely-tone-selector-custom input` ) as HTMLInputElement; + if ( inputElement ) { + inputElement.focus(); + } + }, 0 ); + } } } > - { toneData.emoji } { toneData.label } + { toneData.icon && } + { toneData.label } ); } ) } diff --git a/src/content-helper/common/components/tone-selector/style.scss b/src/content-helper/common/components/tone-selector/style.scss index e7778aaa5..3582d6c66 100644 --- a/src/content-helper/common/components/tone-selector/style.scss +++ b/src/content-helper/common/components/tone-selector/style.scss @@ -2,7 +2,23 @@ .parsely-tone-selector-dropdown { width: 100%; - margin-bottom: to_rem(10px); + overflow-wrap: break-word; + word-break: break-word; + height: to_rem(36px); + align-items: center; + gap: var(--grid-unit-05, to_rem(4px)); + align-self: stretch; + border-radius: 2px; + border: 1px solid var(--Gutenberg-Gray-600, #949494); + background: var(--Gutenberg-White, #fff); + + button { + height: to_rem(33px); + } + + .components-dropdown-menu__toggle.has-icon svg:nth-child(1) { + display: none; + } &.is-disabled { opacity: 0.5; @@ -23,6 +39,7 @@ .parsely-tone-selector-label { flex-grow: 2; text-align: left; + padding: 0 var(--grid-unit-10, to_rem(8px)); &::first-letter { text-transform: uppercase; @@ -30,16 +47,18 @@ } } -.wp-parsely-popover { - - button.components-button.components-menu-item__button.is-selected { - outline: 3px solid transparent; - box-shadow: 0 0 0 2px var(--parsely-green); +.parsely-persona-selector-custom { + width: 100%; - &:focus { - outline: 3px solid transparent; - box-shadow: 0 0 0 2px var(--parsely-green); - } + .components-base-control__field input { + display: flex; + height: to_rem(40px); + padding: var(--grid-unit-15, to_rem(12px)) var(--grid-unit-20, to_rem(16px)); + align-items: center; + gap: var(--grid-unit-05, to_rem(4px)); + align-self: stretch; + border-radius: 2px; + border: 1px solid var(--Gutenberg-Gray-600, #949494); } } diff --git a/src/content-helper/common/content-helper-error.tsx b/src/content-helper/common/content-helper-error.tsx index 4321ac36f..fc37235e1 100644 --- a/src/content-helper/common/content-helper-error.tsx +++ b/src/content-helper/common/content-helper-error.tsx @@ -26,6 +26,10 @@ export enum ContentHelperErrorCode { ParselyApiReturnedNoData = 'ch_parsely_api_returned_no_data', ParselyApiReturnedTooManyResults = 'ch_parsely_api_returned_too_many_results', ParselyApiUnauthorized = 401, // Intentionally without quotes. + ParselyInternalServerError = 500, // Intentionally without quotes. + ParselySchemaValidationFailed = 422, // Intentionally without quotes. + ParselyUpstreamMalformedResponse = 507, // Intentionally without quotes. + ParselyUpstreamNotAvailable = 503, // Intentionally without quotes. PluginCredentialsNotSetMessageDetected = 'parsely_credentials_not_set_message_detected', PluginSettingsApiSecretNotSet = 'parsely_api_secret_not_set', PluginSettingsSiteIdNotSet = 'parsely_site_id_not_set', @@ -64,6 +68,47 @@ export class ContentHelperError extends Error { // Set the prototype explicitly. Object.setPrototypeOf( this, ContentHelperError.prototype ); + + // Errors that need rephrasing. + if ( this.code === ContentHelperErrorCode.ParselyApiUnauthorized ) { + this.message = __( + 'This AI-powered feature is opt-in. To gain access, please submit a request ' + + 'here.', + 'wp-parsely' + ); + } else if ( this.code === ContentHelperErrorCode.ParselyInternalServerError ) { + this.message = __( + 'The Parse.ly API returned an internal server error. Please try again later.', + 'wp-parsely' + ); + } else if ( this.code === ContentHelperErrorCode.HttpRequestFailed && + this.message.includes( 'cURL error 28' ) ) { + this.message = __( + 'The Parse.ly API did not respond in a timely manner. Please try again later.', + 'wp-parsely' + ); + } else if ( this.code === ContentHelperErrorCode.ParselySchemaValidationFailed ) { + this.message = __( + 'The Parse.ly API returned a validation error. Please try again later.', + 'wp-parsely' + ); + } else if ( this.code === ContentHelperErrorCode.ParselyUpstreamMalformedResponse && + this.message.includes( 'Insufficient Storage' ) ) { + this.message = __( + 'The Parse.ly API couldn\'t find any relevant data to fulfill the request. Please retry with a different input.', + 'wp-parsely' + ); + } else if ( this.code === ContentHelperErrorCode.ParselyUpstreamMalformedResponse ) { + this.message = __( + 'The Parse.ly API returned a malformed response. Please try again later.', + 'wp-parsely' + ); + } else if ( this.code === ContentHelperErrorCode.ParselyUpstreamNotAvailable ) { + this.message = __( + 'The Parse.ly API is currently unavailable. Please try again later.', + 'wp-parsely' + ); + } } /** @@ -104,14 +149,6 @@ export class ContentHelperError extends Error { ) ); } - // Errors that need rephrasing. - if ( this.code === ContentHelperErrorCode.ParselyApiUnauthorized ) { - this.message = __( - 'This feature is accessible to select customers participating in its beta testing.', - 'wp-parsely' - ); - } - return ( div:first-of-type { + margin-top: calc(-1 * var(--grid-unit-20, to_rem(16px))); + } + + .components-panel__body-title { + margin: 0 calc(-1 * var(--grid-unit-20, to_rem(16px))) to_rem(6px); + padding-bottom: 0; + } + } + + .components-panel__body-toggle.components-button { + padding: var(--grid-unit-20, to_rem(16px)) var(--grid-unit-20, to_rem(16px)); + color: var(--Gutenberg-Gray-900, #1e1e1e); + font-size: to_rem(11px); + font-style: normal; + font-weight: 600; + line-height: to_rem(16px); /* 145.455% */ + text-transform: uppercase; + + .components-panel__arrow { + margin-right: 0; + } + } + + .components-panel__body { + + &.is-opened { + padding: 0; + } + } +} diff --git a/src/content-helper/common/css/variables.scss b/src/content-helper/common/css/variables.scss index b38bbb0b0..31d008fab 100644 --- a/src/content-helper/common/css/variables.scss +++ b/src/content-helper/common/css/variables.scss @@ -7,9 +7,11 @@ $html-font-size: 16px; // Used in functions.scss. * variables in the end of the file. */ .settings_page_parsely, +.wp-parsely-panel, .wp-parsely-content-helper, .wp-parsely-excerpt-generator, .wp-parsely-popover, +.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"], // Sidebar icon. #wp-parsely-dashboard-widget { /** Layout section - base.scss. **/ @@ -18,6 +20,8 @@ $html-font-size: 16px; // Used in functions.scss. /** Category colors section - base scss. **/ --parsely-green: hsl(107, 42%, 46%); + --parsely-green-10: hsla(98, 61%, 81%, 1); + --parsely-green-65: hsla(109, 59%, 26%, 1); --gray-200: #f7f8f9; --gray-300: #edeeef; --gray-400: #d7dbdf; @@ -44,9 +48,20 @@ $html-font-size: 16px; // Used in functions.scss. --data: var(--green-500); --control: var(--blue-500); + /** Grid section **/ + --grid-unit-5: 0.25rem; // 4px. + --grid-unit-10: 0.5rem; // 8px. + --grid-unit-15: 0.75rem; // 12px. + --grid-unit-20: 1rem; // 16px. + + /** Additional variables. **/ - --font-size--large: 1rem; + --font-size--smaller: 0.688rem; // 11px. + --font-size--small: 0.75rem; // 12px. + --font-size--medium: 0.875rem; // 14px. + --font-size--large: 1rem; // 16px. --font-size--extra-large: 1.2rem; --black: #000; --sidebar-black: #1e1e1e; + --sidebar-white: #f0f0f0; } diff --git a/src/content-helper/common/hooks/useSaveSettings.ts b/src/content-helper/common/hooks/useSaveSettings.ts deleted file mode 100644 index 470bdf909..000000000 --- a/src/content-helper/common/hooks/useSaveSettings.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * WordPress dependencies - */ -import apiFetch from '@wordpress/api-fetch'; -import { useEffect, useRef } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { TopPostsSettings } from '../../dashboard-widget/components/top-posts'; -import { SidebarSettings } from '../../editor-sidebar/editor-sidebar'; - -/** - * Custom types for brevity and for avoiding a "type React is undefined" error. - */ -type Settings = SidebarSettings | TopPostsSettings; -type ReactDeps = React.DependencyList | undefined; - -/** - * Saves the settings into the WordPress database whenever a dependency update - * occurs. - * - * @since 3.13.0 - * - * @param {string} endpoint The settings endpoint to send the data to. - * @param {Settings} data The data to send. - * @param {ReactDeps} deps The deps array that triggers saving. - */ -export const useSaveSettings = ( - endpoint: string, - data: Settings, - deps: ReactDeps = undefined -) => { - const isFirstRender = useRef( true ); - - useEffect( () => { - // Don't save settings on the first render. - if ( isFirstRender.current ) { - isFirstRender.current = false; - return; - } - - apiFetch( { - path: '/wp-parsely/v1/user-meta/content-helper/' + endpoint, - method: 'PUT', - data, - } ); - }, deps ?? Object.values( data ) ); // eslint-disable-line react-hooks/exhaustive-deps -}; diff --git a/src/content-helper/common/icons/ai-icon.tsx b/src/content-helper/common/icons/ai-icon.tsx new file mode 100644 index 000000000..403de3652 --- /dev/null +++ b/src/content-helper/common/icons/ai-icon.tsx @@ -0,0 +1,19 @@ +import { Path, SVG } from '@wordpress/components'; + +export const AiIcon = ( { size = 24, className = 'wp-parsely-icon' }: { size?: number, className: string } ): JSX.Element => { + return ( + + + + + + ); +}; diff --git a/src/content-helper/common/icons/clock-icon.tsx b/src/content-helper/common/icons/clock-icon.tsx new file mode 100644 index 000000000..d8dbf7ec9 --- /dev/null +++ b/src/content-helper/common/icons/clock-icon.tsx @@ -0,0 +1,15 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export const ClockIcon = () => ( + + + + + + +); diff --git a/src/content-helper/common/icons/leaf-icon.tsx b/src/content-helper/common/icons/leaf-icon.tsx index 1570b5b01..e645230ba 100644 --- a/src/content-helper/common/icons/leaf-icon.tsx +++ b/src/content-helper/common/icons/leaf-icon.tsx @@ -3,8 +3,9 @@ */ import { Path, SVG } from '@wordpress/components'; -export const LeafIcon = ( { size = 24 } ) => ( +export const LeafIcon = ( { size = 24, className = 'wp-parsely-icon' } ) => ( ( + + + +); diff --git a/src/content-helper/common/icons/views-icon.tsx b/src/content-helper/common/icons/views-icon.tsx index b3f306cb4..5de6a1efd 100644 --- a/src/content-helper/common/icons/views-icon.tsx +++ b/src/content-helper/common/icons/views-icon.tsx @@ -3,16 +3,17 @@ */ import { Path, SVG } from '@wordpress/components'; -export const ViewsIcon = () => ( +export const ViewsIcon = ( { size = 16 }: { size?: number } ) => ( + { __( 'Number of Views', 'wp-parsely' ) } @@ -76,7 +78,7 @@ export function PostListItemMetric( if ( 'avg_engaged' === metric ) { return ( - + { __( 'Average Time', 'wp-parsely' ) } @@ -86,7 +88,7 @@ export function PostListItemMetric( } return ( - + - ); diff --git a/src/content-helper/dashboard-widget/class-dashboard-widget.php b/src/content-helper/dashboard-widget/class-dashboard-widget.php index e9e39c248..0e4b042a3 100644 --- a/src/content-helper/dashboard-widget/class-dashboard-widget.php +++ b/src/content-helper/dashboard-widget/class-dashboard-widget.php @@ -94,7 +94,7 @@ public function can_enable_widget(): bool { return $this->can_enable_feature( null !== $screen && 'dashboard' === $screen->id, - $posts_api->is_user_allowed_to_make_api_call() + $posts_api->is_available_to_current_user() ); } diff --git a/src/content-helper/dashboard-widget/components/top-posts.tsx b/src/content-helper/dashboard-widget/components/top-posts.tsx index 104d8bcc1..9f0b7d17a 100644 --- a/src/content-helper/dashboard-widget/components/top-posts.tsx +++ b/src/content-helper/dashboard-widget/components/top-posts.tsx @@ -10,8 +10,8 @@ import { __, sprintf } from '@wordpress/i18n'; */ import { Telemetry } from '../../../js/telemetry/telemetry'; import { ContentHelperError } from '../../common/content-helper-error'; -import { useSaveSettings } from '../../common/hooks/useSaveSettings'; import { Select } from '../../common/select'; +import { TopPostsSettings, useSettings } from '../../common/settings'; import { Metric, Period, @@ -25,74 +25,18 @@ import { TopPostListItem } from './top-posts-list-item'; const FETCH_RETRIES = 1; -/** - * Defines the settings structure for the TopPosts component. - * - * @since 3.13.0 - */ -export interface TopPostsSettings { - Metric: Metric; - Period: Period; -} - -/** - * Gets the settings from the passed JSON. - * - * If missing settings or invalid values are detected, they get set to their - * defaults. - * - * @since 3.13.0 - * - * @param {string} settingsJson The JSON containing the settings. - * - * @return {TopPostsSettings} The resulting settings object. - */ -const getSettingsFromJson = ( settingsJson: string ): TopPostsSettings => { - let parsedSettings: TopPostsSettings; - - try { - parsedSettings = JSON.parse( settingsJson ); - } catch ( e ) { - // Return defaults when parsing failed or the string is empty. - return { - Metric: Metric.Views, - Period: Period.Days7, - }; - } - - // Fix invalid values if any are found. - if ( ! isInEnum( parsedSettings?.Metric, Metric ) ) { - parsedSettings.Metric = Metric.Views; - } - if ( ! isInEnum( parsedSettings?.Period, Period ) ) { - parsedSettings.Period = Period.Days7; - } - - return parsedSettings; -}; - /** * List of the top posts. * * @since 3.7.0 */ export function TopPosts(): JSX.Element { - const [ settings, setSettings ] = useState( - getSettingsFromJson( window.wpParselyContentHelperSettings ) - ); + const { settings, setSettings } = useSettings(); const [ loading, setLoading ] = useState( true ); const [ error, setError ] = useState(); const [ posts, setPosts ] = useState( [] ); const [ page, setPage ] = useState( 1 ); - /** - * Saves the settings into the WordPress database whenever a setting change - * occurs. - * - * @since 3.13.0 - */ - useSaveSettings( 'dashboard-widget-settings', settings ); - /** * Fetches the top posts. * @@ -152,7 +96,6 @@ export function TopPosts(): JSX.Element { onChange={ ( event ) => { if ( isInEnum( event.target.value, Period ) ) { setSettings( { - ...settings, Period: event.target.value as Period, } ); trackFilterChanges( 'period', { period: event.target.value } ); @@ -170,7 +113,6 @@ export function TopPosts(): JSX.Element { onChange={ ( event ) => { if ( isInEnum( event.target.value, Metric ) ) { setSettings( { - ...settings, Metric: event.target.value as Metric, } ); trackFilterChanges( 'metric', { metric: event.target.value } ); diff --git a/src/content-helper/dashboard-widget/dashboard-widget.scss b/src/content-helper/dashboard-widget/dashboard-widget.scss index 09f5ac85f..dffc9eead 100644 --- a/src/content-helper/dashboard-widget/dashboard-widget.scss +++ b/src/content-helper/dashboard-widget/dashboard-widget.scss @@ -137,7 +137,8 @@ } } - .parsely-top-post-metric-data { + // Common class name shared with the PCH Editor Sidebar. + .parsely-post-metric-data { float: right; font-family: var(--numeric-font); font-size: to_rem(18px); diff --git a/src/content-helper/dashboard-widget/dashboard-widget.tsx b/src/content-helper/dashboard-widget/dashboard-widget.tsx index c294411f9..09959ee2f 100644 --- a/src/content-helper/dashboard-widget/dashboard-widget.tsx +++ b/src/content-helper/dashboard-widget/dashboard-widget.tsx @@ -8,6 +8,45 @@ import { createRoot, render } from '@wordpress/element'; */ import { VerifyCredentials } from '../common/verify-credentials'; import { TopPosts } from './components/top-posts'; +import { SettingsProvider, TopPostsSettings } from '../common/settings'; +import { isInEnum, Metric, Period } from '../common/utils/constants'; + +/** + * Gets the settings from the passed JSON. + * + * If missing settings or invalid values are detected, they get set to their + * defaults. + * + * @since 3.13.0 + * @since 3.14.0 Moved from `content-helper/dashboard-widget/dashboard-widget.tsx`. + * + * @param {string} settingsJson The JSON containing the settings. + * + * @return {TopPostsSettings} The resulting settings object. + */ +const getSettingsFromJson = ( settingsJson: string ): TopPostsSettings => { + let parsedSettings: TopPostsSettings; + + try { + parsedSettings = JSON.parse( settingsJson ); + } catch ( e ) { + // Return defaults when parsing failed or the string is empty. + return { + Metric: Metric.Views, + Period: Period.Days7, + }; + } + + // Fix invalid values if any are found. + if ( ! isInEnum( parsedSettings?.Metric, Metric ) ) { + parsedSettings.Metric = Metric.Views; + } + if ( ! isInEnum( parsedSettings?.Period, Period ) ) { + parsedSettings.Period = Period.Days7; + } + + return parsedSettings; +}; window.addEventListener( 'load', @@ -15,7 +54,15 @@ window.addEventListener( const container = document.querySelector( '#wp-parsely-dashboard-widget > .inside' ); if ( null !== container ) { - const component = ; + const component = + + + + + ; if ( createRoot ) { createRoot( container ).render( component ); diff --git a/src/content-helper/dashboard-widget/provider.ts b/src/content-helper/dashboard-widget/provider.ts index 25ebdb94b..534452dea 100644 --- a/src/content-helper/dashboard-widget/provider.ts +++ b/src/content-helper/dashboard-widget/provider.ts @@ -14,7 +14,7 @@ import { } from '../common/content-helper-error'; import { getApiPeriodParams } from '../common/utils/api'; import { PostData } from '../common/utils/post'; -import { TopPostsSettings } from './components/top-posts'; +import { TopPostsSettings } from '../common/settings'; /** * The form of the response returned by the /stats/posts WordPress REST API diff --git a/src/content-helper/editor-sidebar/class-editor-sidebar.php b/src/content-helper/editor-sidebar/class-editor-sidebar.php index 1dd1fd6ae..d8f20857a 100644 --- a/src/content-helper/editor-sidebar/class-editor-sidebar.php +++ b/src/content-helper/editor-sidebar/class-editor-sidebar.php @@ -10,10 +10,12 @@ namespace Parsely\Content_Helper; +use Parsely\Dashboard_Link; use Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint; use Parsely\Parsely; use Parsely\Content_Helper\Content_Helper_Feature; +use WP_Post; use function Parsely\Utils\get_asset_info; use const Parsely\PARSELY_FILE; @@ -69,6 +71,35 @@ public static function get_style_id(): string { return static::get_script_id(); } + /** + * Returns the Parse.ly post dashboard URL for the current post. + * + * @since 3.14.0 + * + * @param int|null|WP_Post $post_id The post ID or post object. Default is the current post. + * @return string|null The Parse.ly post dashboard URL, or false if the post ID is invalid. + */ + private function get_parsely_post_url( $post_id = null ): ?string { + // Get permalink for the post. + $post_id = $post_id ?? get_the_ID(); + if ( false === $post_id ) { + return null; + } + + /** + * The post object. + * + * @var WP_Post $post + */ + $post = get_post( $post_id ); + + if ( ! Dashboard_Link::can_show_link( $post, $this->parsely ) ) { + return null; + } + + return Dashboard_Link::generate_url( $post, $this->parsely->get_site_id(), 'wp-page-single', 'editor-sidebar' ); + } + /** * Inserts the PCH Editor Sidebar assets. * @@ -92,6 +123,16 @@ public function run(): void { $this->inject_inline_scripts( Editor_Sidebar_Settings_Endpoint::get_route() ); + // Inject inline variables for the editor sidebar. + $parsely_post_url = $this->get_parsely_post_url(); + if ( null !== $parsely_post_url ) { + wp_add_inline_script( + static::get_script_id(), + 'wpParselyPostUrl = ' . wp_json_encode( $parsely_post_url ) . ';', + 'before' + ); + } + wp_enqueue_style( static::get_style_id(), $built_assets_url . 'editor-sidebar.css', diff --git a/src/content-helper/editor-sidebar/editor-sidebar.scss b/src/content-helper/editor-sidebar/editor-sidebar.scss index db44747d3..8d43f0f54 100644 --- a/src/content-helper/editor-sidebar/editor-sidebar.scss +++ b/src/content-helper/editor-sidebar/editor-sidebar.scss @@ -1,445 +1,98 @@ @import "../common/css/variables"; @import "../common/css/functions"; -.wp-parsely-content-helper { - - .parsely-spinner-wrapper { - display: flex; - justify-content: center; - margin: to_rem(40px) 0; +// Set the colors of the sidebar icon. +.components-button[aria-controls="wp-parsely-block-editor-sidebar:wp-parsely-content-helper"] { - svg { - width: 22px; - height: 22px; - } + &:hover { + background-color: #fff; } - .content-helper-error-message { - margin-top: to_rem(15px) !important; - } - - p.content-helper-error-message-hint { - color: var(--gray-700); + &:focus { + background-color: #fff; } -} - -.wp-parsely-content-helper .parsely-top-posts-wrapper { - .parsely-top-posts { - list-style-type: none; - margin: to_rem(22px) 0 0; - } + &.is-pressed, + &.is-pressed:hover { - p.parsely-top-posts-descr { - color: var(--gray-700); - margin-top: to_rem(15px); - } + background-color: var(--parsely-green-65); - .parsely-top-post { - border-top: 1px solid var(--gray-300); - margin-bottom: to_rem(5px); - padding: to_rem(10px) 0; - } - - .parsely-top-post-title a { - text-decoration: none; - line-height: 16px; - } - - .parsely-top-post-stats-link { - color: var(--black); - font-size: to_rem(14px); - margin-right: to_rem(7px); - - &:hover { - color: var(--blue-550); - } - } - - .parsely-top-post-metric-data { - - svg, - span.dashicon { - position: relative; - top: 2px; - margin-right: to_rem(3px); - fill: var(--gray-600); - } - - span.dashicon { - color: var(--gray-600); - font-size: 16px; - width: 16px; - height: 16px; + .wp-parsely-sidebar-icon path { + fill: var(--parsely-green-10); } } +} - .parsely-top-post-view-link, - .parsely-top-post-edit-link { - display: inline-block; - width: 16px; - height: 16px; - position: relative; - margin-right: to_rem(3px); - - svg { - position: absolute; - top: 2px; - fill: #8d98a1; - } - - &:hover svg { - fill: var(--blue-550); - } - } +.wp-parsely-content-helper { - .parsely-top-post-info { + .wp-parsely-sidebar-header { display: flex; - margin: to_rem(5px) 0 0; - justify-content: space-between; - align-items: center; + flex-direction: column; + align-items: flex-start; + gap: to_rem(24px); + align-self: stretch; - >span { - color: var(--gray-600); - margin-bottom: 0; + .components-button { display: flex; - - &:not(:first-child) { - margin-left: to_rem(5px); - } - } - } -} - -.wp-parsely-content-helper .performance-details-panel { - - // Generic styles for all sections. - div.section { - font-family: var(--base-font); - margin-top: 1.8rem; - - table { - border-collapse: collapse; + justify-content: center; + align-items: center; width: 100%; - - th { - font-weight: 400; - text-align: left; - } - } - - // Generic styles for section titles. - div.section-title { - color: var(--base-text-2); - margin-bottom: 0.5rem; - } - } - - // Data Period section. - div.section.period { - margin-top: 0.8rem; - color: var(--base-text-2); - } - - // General Performance section (Views, Visitors, Time). - div.section.general-performance { - - table { - - // Metrics. - tbody tr { - font-family: var(--numeric-font); - font-size: var(--font-size--extra-large); - font-weight: 500; - } - - // Titles. - tfoot tr { - color: var(--gray-700); - height: 1.4rem; - vertical-align: bottom; - } } } - // Referrer Types section. - div.section.referrer-types { - - // Multi-percentage bar. - div.multi-percentage-bar { - --radius: 2px; - display: flex; - height: 0.5rem; - - .bar-fill { - - // Border radiuses for first and last bar-fills. - &:first-child { - border-radius: var(--radius) 0 0 var(--radius); - } - - &:last-child { - border-radius: 0 var(--radius) var(--radius) 0; - } - - // Bar-fill colors by referrer type. - &.direct { - background-color: hsl(var(--ref-direct)); - } - - &.internal { - background-color: hsl(var(--ref-internal)); - } - - &.search { - background-color: hsl(var(--ref-search)); - } + .wp-parsely-sidebar-main-panel { - &.social { - background-color: hsl(var(--ref-social)); - } - - &.other { - background-color: hsl(var(--ref-other)); - } - } - } + .wp-parsely-sidebar-tabs { - // Table showing referrer types and metrics. - table { - margin-top: 0.5rem; + .components-tab-panel__tabs { - // Metrics. - tbody tr { - font-family: var(--numeric-font); - font-size: var(--font-size--large); - height: 1.4rem; - vertical-align: bottom; - } - } - } - - // Top Referrers section. - div.section.top-referrers { - - table { - - // Titles (Top Referrers, Page Views). - thead tr { - color: var(--base-text-2); - height: 1.6rem; - vertical-align: top; - - th:last-child { - text-align: right; + // Individual tab. + button { + display: flex; + height: to_rem(48px); + flex-direction: column; + align-items: center; + flex: 1 0 0; } - } - - // Table rows. - tbody { - - tr { - border: 1px solid var(--border); - border-left: 0; - border-right: 0; - height: 2rem; - - // Referrer name column. - th:first-child { - --width: 8rem; - // Use min and max width for text truncation to work. - max-width: var(--width); - min-width: var(--width); - overflow: hidden; - padding-right: 1rem; - text-overflow: ellipsis; - white-space: nowrap; - } - - // Percentage bar column. - td:nth-child(2) { - width: 100%; - } - // Views column. - td:last-child { - padding-left: 1rem; - text-align: right; - } + // Divider line. + .components-tab-panel__tabs-item::after { + height: calc(var(--wp-admin-border-width-focus) * 1); + outline: 2px solid transparent; + outline-offset: -1px; + background: var(--gray-400); } - // Percentage bar. - div.percentage-bar { - // Bar background. - --radius: 4px; - background-color: var(--base-3); - border-radius: var(--radius); - display: flex; - height: 0.4rem; - margin: 0; - overflow: hidden; - - // Bar fill. - &::after { - background-color: var(--data); - border-radius: var(--radius); - content: ""; - height: 100%; - width: var(--bar-fill); - } + // Make the active tab divider WP blue. + .components-tab-panel__tabs-item.is-active::after { + background: var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)); } } - } - - // Percentage text below table. - div:last-child { - color: var(--base-text-2); - margin-top: 0.6rem; - } - } - - // Actions section (Visit Post, View in Parse.ly buttons). - div.section.actions { - display: inline-flex; - justify-content: space-between; - width: 100%; - - a.components-button { - border-radius: 4px; - text-transform: uppercase; - - // Visit Post. - &.is-secondary { - box-shadow: inset 0 0 0 1px var(--border); - color: var(--sidebar-black); - } - - // View in Parse.ly. - &.is-primary { - background-color: var(--control); - } - } - } -} -// Title Suggestion Panel CSS. -.wp-parsely-content-helper .parsely-write-titles-wrapper { - display: flex; - flex-direction: column; - .parsely-write-titles-text { - margin-bottom: to_rem(10px); - - strong { - text-transform: lowercase; } } - // Generate Titles button. - .parsely-write-titles-generate-button { - margin: to_rem(10px) 0; - justify-content: center; + .parsely-spinner-wrapper { display: flex; - } - - .parsely-write-titles-settings { - - .parsely-write-titles-settings-header { - margin: to_rem(10px) 0; - display: flex; - width: 100%; - - .parsely-write-titles-settings-header-label { - margin-left: to_rem(5px); - flex-grow: 2; - text-align: left; - - .components-base-control__field { - display: flex; - align-items: center; - justify-content: space-between; + justify-content: center; + margin: to_rem(40px) 0; - .components-base-control__label { - margin-bottom: 0; - } - } - } + svg { + width: 22px; + height: 22px; } } - // Accepted Title view. - .parsely-write-titles-accepted-title-container { - - .parsely-write-titles-accepted-title { - font-weight: 600; - font-size: to_rem(16px); - line-height: to_rem(20px); - margin: 0 0 to_rem(15px) 0; - text-align: center; - } - - // Accepted Title actions. - .parsely-write-titles-accepted-title-actions { - margin: to_rem(10px) 0; - justify-content: center; - display: flex; - gap: to_rem(10px); - } + .content-helper-error-message { + margin-top: to_rem(15px) !important; } - // Container for the title suggestions. - .parsely-write-titles-title-suggestions-container { - margin: to_rem(10px) 0 0; - - // Single title suggestion. - .parsely-write-titles-title-suggestion { - border-top: 1px solid var(--gray-300); - display: flex; - padding: 1rem 0; - - .parsely-write-titles-suggested-title { - flex: 1; - font-weight: 600; - font-size: to_rem(14px); - padding: 0 to_rem(8px) 0 to_rem(5px); - } - - .parsely-write-titles-suggested-title-actions { - display: flex; - flex-direction: column; - width: to_rem(25px); - - .components-button-group .components-button { - margin-left: 0; - } - - .is-pinned { - background-color: var(--gray-500); - color: #fff; - box-shadow: inset 0 0 0 1px var(--sidebar-black); - } - } - - &.original-title { - background-color: var(--gray-200); - border: 0; - color: var(--gray-600); - margin-bottom: to_rem(15px); - padding: to_rem(5px); - - .parsely-write-titles-suggested-title { - padding-bottom: to_rem(5px); - padding-top: to_rem(5px); - } - - button { - margin-top: to_rem(2px); - } - } - } + .wp-parsely-content-helper-error .content-helper-error-message { + margin-top: to_rem(15px) !important; + } - .title-in-use { - color: var(--green-900); - } + p.content-helper-error-message-hint { + color: var(--gray-700); } } diff --git a/src/content-helper/editor-sidebar/editor-sidebar.tsx b/src/content-helper/editor-sidebar/editor-sidebar.tsx index 34091e5e1..31a087d7d 100644 --- a/src/content-helper/editor-sidebar/editor-sidebar.tsx +++ b/src/content-helper/editor-sidebar/editor-sidebar.tsx @@ -1,58 +1,45 @@ /** * WordPress dependencies */ -import { Panel, PanelBody, SelectControl } from '@wordpress/components'; -// eslint-disable-next-line import/named -import { Taxonomy, User, store as coreStore } from '@wordpress/core-data'; +import { + Panel, + TabPanel, +} from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { PluginSidebar } from '@wordpress/edit-post'; -import { store as editorStore } from '@wordpress/editor'; -import { useEffect, useMemo, useState } from '@wordpress/element'; +import { useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { chartBar as ChartIcon } from '@wordpress/icons'; import { registerPlugin } from '@wordpress/plugins'; /** * Internal dependencies */ import { Telemetry } from '../../js/telemetry/telemetry'; -import { BetaBadge } from '../common/components/beta-badge'; -import { PARSELY_PERSONAS } from '../common/components/persona-selector'; -import { PARSELY_TONES } from '../common/components/tone-selector'; -import { useSaveSettings } from '../common/hooks/useSaveSettings'; +import { EditIcon } from '../common/icons/edit-icon'; import { LeafIcon } from '../common/icons/leaf-icon'; +import { + SettingsProvider, + SidebarSettings, + useSettings, +} from '../common/settings'; import { Metric, Period, PostFilterType, - getMetricDescription, - getPeriodDescription, isInEnum, } from '../common/utils/constants'; -import { VerifyCredentials } from '../common/verify-credentials'; -import { PerformanceDetails } from './performance-details/component'; -import { RelatedTopPostList } from './related-top-posts/component-list'; -import { TitleSuggestionsPanel } from './title-suggestions/component'; +import { + DEFAULT_MAX_LINKS, + DEFAULT_MAX_LINK_WORDS, + initSmartLinking, +} from './smart-linking/smart-linking'; +import { SidebarPerformanceTab } from './tabs/sidebar-performance-tab'; +import { SidebarToolsTab } from './tabs/sidebar-tools-tab'; const BLOCK_PLUGIN_ID = 'wp-parsely-block-editor-sidebar'; -/** - * Defines the settings structure for the ContentHelperEditorSidebar component. - * - * @since 3.13.0 - */ -export interface SidebarSettings { - PerformanceDetailsOpen: boolean; - RelatedTopPostsFilterBy: string; - RelatedTopPostsFilterValue: string; - RelatedTopPostsOpen: boolean; - SettingsMetric: Metric; - SettingsOpen: boolean; - SettingsPeriod: Period; - TitleSuggestionsOpen: boolean; - TitleSuggestionsPersona: string; - TitleSuggestionsSettingsOpen: boolean; - TitleSuggestionsTone: string; -} +export type OnSettingChangeFunction = ( key: keyof SidebarSettings, value: string | boolean | number ) => void; /** * Defines the data structure exposed by the Sidebar about the currently opened @@ -66,18 +53,6 @@ export interface SidebarPostData { tags: string[]; } -/** - * Defines typings for some non-exported Gutenberg functions to avoid - * intellisense errors in function calls. - * - * This can be removed once Gutenberg provides typings for these functions. - * - * @since 3.11.0 - */ -interface GutenbergFunction { - getEditedPostAttribute( attribute: string ): number[]; -} - /** * Gets the settings from the passed JSON. * @@ -91,64 +66,100 @@ interface GutenbergFunction { * * @return {SidebarSettings} The resulting settings object. */ -const getSettingsFromJson = ( settingsJson: string ): SidebarSettings => { - let parsedSettings: SidebarSettings; +export const getSettingsFromJson = ( settingsJson: string = '' ): SidebarSettings => { + // Default settings object. + const defaultSettings: SidebarSettings = { + InitialTabName: 'tools', + PerformanceStatsSettings: { + Period: Period.Days7, + VisiblePanels: [ 'overview', 'categories', 'referrers' ], + VisibleDataPoints: [ 'views', 'visitors', 'avgEngaged', 'recirculation' ], + }, + RelatedPostsFilterBy: PostFilterType.Unavailable, + RelatedPostsFilterValue: '', + RelatedPostsMetric: Metric.Views, + RelatedPostsOpen: false, + RelatedPostsPeriod: Period.Days7, + SmartLinkingMaxLinks: DEFAULT_MAX_LINKS, + SmartLinkingMaxLinkWords: DEFAULT_MAX_LINK_WORDS, + SmartLinkingOpen: false, + TitleSuggestionsSettings: { + Open: false, + Tone: 'neutral', + Persona: 'journalist', + }, + }; + + // If the settings are empty, try to get them from the global variable. + if ( '' === settingsJson ) { + settingsJson = window.wpParselyContentHelperSettings; + } + let parsedSettings: SidebarSettings; try { parsedSettings = JSON.parse( settingsJson ); } catch ( e ) { // Return defaults when parsing failed or the string is empty. - return { - PerformanceDetailsOpen: true, - RelatedTopPostsFilterBy: PostFilterType.Unavailable, - RelatedTopPostsFilterValue: '', - RelatedTopPostsOpen: false, - SettingsMetric: Metric.Views, - SettingsOpen: true, - SettingsPeriod: Period.Days7, - TitleSuggestionsOpen: false, - TitleSuggestionsPersona: PARSELY_PERSONAS.journalist.label, - TitleSuggestionsSettingsOpen: false, - TitleSuggestionsTone: PARSELY_TONES.neutral.label, - }; + return defaultSettings; } + // Merge parsed settings with default settings. + const mergedSettings = { ...defaultSettings, ...parsedSettings }; + // Fix invalid values if any are found. - if ( typeof parsedSettings?.PerformanceDetailsOpen !== 'boolean' ) { - parsedSettings.PerformanceDetailsOpen = true; + if ( typeof mergedSettings.InitialTabName !== 'string' ) { + mergedSettings.InitialTabName = defaultSettings.InitialTabName; } - if ( ! isInEnum( parsedSettings?.RelatedTopPostsFilterBy, PostFilterType ) ) { - parsedSettings.RelatedTopPostsFilterBy = PostFilterType.Unavailable; + if ( typeof mergedSettings.PerformanceStatsSettings !== 'object' ) { + mergedSettings.PerformanceStatsSettings = defaultSettings.PerformanceStatsSettings; } - if ( typeof parsedSettings?.RelatedTopPostsFilterValue !== 'string' ) { - parsedSettings.RelatedTopPostsFilterValue = ''; + if ( ! isInEnum( mergedSettings.PerformanceStatsSettings.Period, Period ) ) { + mergedSettings.PerformanceStatsSettings.Period = defaultSettings.PerformanceStatsSettings.Period; } - if ( typeof parsedSettings?.RelatedTopPostsOpen !== 'boolean' ) { - parsedSettings.RelatedTopPostsOpen = false; + if ( ! Array.isArray( mergedSettings.PerformanceStatsSettings.VisiblePanels ) ) { + mergedSettings.PerformanceStatsSettings.VisiblePanels = defaultSettings.PerformanceStatsSettings.VisiblePanels; } - if ( ! isInEnum( parsedSettings?.SettingsMetric, Metric ) ) { - parsedSettings.SettingsMetric = Metric.Views; + if ( ! Array.isArray( mergedSettings.PerformanceStatsSettings.VisibleDataPoints ) ) { + mergedSettings.PerformanceStatsSettings.VisibleDataPoints = defaultSettings.PerformanceStatsSettings.VisibleDataPoints; } - if ( typeof parsedSettings?.SettingsOpen !== 'boolean' ) { - parsedSettings.SettingsOpen = true; + if ( ! isInEnum( mergedSettings.RelatedPostsFilterBy, PostFilterType ) ) { + mergedSettings.RelatedPostsFilterBy = defaultSettings.RelatedPostsFilterBy; } - if ( ! isInEnum( parsedSettings?.SettingsPeriod, Period ) ) { - parsedSettings.SettingsPeriod = Period.Days7; + if ( typeof mergedSettings.RelatedPostsFilterValue !== 'string' ) { + mergedSettings.RelatedPostsFilterValue = defaultSettings.RelatedPostsFilterValue; } - if ( typeof parsedSettings?.TitleSuggestionsOpen !== 'boolean' ) { - parsedSettings.TitleSuggestionsOpen = false; + if ( ! isInEnum( mergedSettings.RelatedPostsMetric, Metric ) ) { + mergedSettings.RelatedPostsMetric = defaultSettings.RelatedPostsMetric; } - if ( typeof parsedSettings?.TitleSuggestionsPersona !== 'string' ) { - parsedSettings.TitleSuggestionsPersona = PARSELY_PERSONAS.journalist.label; + if ( typeof mergedSettings.RelatedPostsOpen !== 'boolean' ) { + mergedSettings.RelatedPostsOpen = defaultSettings.RelatedPostsOpen; } - if ( typeof parsedSettings?.TitleSuggestionsSettingsOpen !== 'boolean' ) { - parsedSettings.TitleSuggestionsSettingsOpen = false; + if ( ! isInEnum( mergedSettings.RelatedPostsPeriod, Period ) ) { + mergedSettings.RelatedPostsPeriod = defaultSettings.RelatedPostsPeriod; } - if ( typeof parsedSettings?.TitleSuggestionsTone !== 'string' ) { - parsedSettings.TitleSuggestionsTone = PARSELY_TONES.neutral.label; + if ( typeof mergedSettings.SmartLinkingMaxLinks !== 'number' ) { + mergedSettings.SmartLinkingMaxLinks = defaultSettings.SmartLinkingMaxLinks; + } + if ( typeof mergedSettings.SmartLinkingMaxLinkWords !== 'number' ) { + mergedSettings.SmartLinkingMaxLinkWords = defaultSettings.SmartLinkingMaxLinkWords; + } + if ( typeof mergedSettings.SmartLinkingOpen !== 'boolean' ) { + mergedSettings.SmartLinkingOpen = defaultSettings.SmartLinkingOpen; + } + if ( typeof mergedSettings.TitleSuggestionsSettings !== 'object' ) { + mergedSettings.TitleSuggestionsSettings = defaultSettings.TitleSuggestionsSettings; + } + if ( typeof mergedSettings.TitleSuggestionsSettings.Open !== 'boolean' ) { + mergedSettings.TitleSuggestionsSettings.Open = defaultSettings.TitleSuggestionsSettings.Open; + } + if ( typeof mergedSettings.TitleSuggestionsSettings.Tone !== 'string' ) { + mergedSettings.TitleSuggestionsSettings.Tone = defaultSettings.TitleSuggestionsSettings.Tone; + } + if ( typeof mergedSettings.TitleSuggestionsSettings.Persona !== 'string' ) { + mergedSettings.TitleSuggestionsSettings.Persona = defaultSettings.TitleSuggestionsSettings.Persona; } - return parsedSettings; + return mergedSettings; }; /** @@ -159,115 +170,7 @@ const getSettingsFromJson = ( settingsJson: string ): SidebarSettings => { * @return {JSX.Element} The Content Helper Editor Sidebar. */ const ContentHelperEditorSidebar = (): JSX.Element => { - const [ settings, setSettings ] = useState( - getSettingsFromJson( window.wpParselyContentHelperSettings ) - ); - const [ postData, setPostData ] = useState( { - authors: [], categories: [], tags: [], - } ); - - /** - * Updates all filter settings. - * - * @since 3.13.0 - * - * @param {PostFilterType} filter The new filter type. - * @param {string} value The new filter value. - */ - const handleRelatedTopPostsFilterChange = ( - filter: PostFilterType, value: string - ): void => { - setSettings( { - ...settings, - RelatedTopPostsFilterBy: filter, - RelatedTopPostsFilterValue: value, - } ); - }; - - /** - * Updates the passed setting. - * - * @since 3.13.0 - * - * @param {keyof SidebarSettings} setting The setting to be updated. - * @param {string|boolean} value The new settings value. - */ - const handleSettingChange = ( - setting: keyof SidebarSettings, value: string|boolean - ): void => { - setSettings( { ...settings, [ setting ]: value } ); - }; - - /** - * Returns the current Post's ID, tags and categories. - * - * @since 3.11.0 - */ - const { authors, categories, tags } = useSelect( ( select ) => { - const { getEditedPostAttribute } = select( editorStore ) as GutenbergFunction; - const { getEntityRecords } = select( coreStore ); - - const authorRecords: User[] | null = getEntityRecords( - 'root', 'user', { include: getEditedPostAttribute( 'author' ) } - ); - - const categoryRecords: Taxonomy[] | null = getEntityRecords( - 'taxonomy', 'category', { include: getEditedPostAttribute( 'categories' ) } - ); - - const tagRecords: Taxonomy[]|null = getEntityRecords( - 'taxonomy', 'post_tag', { include: getEditedPostAttribute( 'tags' ) } - ); - - return { - authors: authorRecords, - categories: categoryRecords, - tags: tagRecords, - }; - }, [] ); - - /** - * Returns the current Post's tag names. - * - * @since 3.11.0 - */ - const tagNames = useMemo( () => { - return tags ? tags.map( ( t ) => t.name ) : []; - }, [ tags ] ); - - /** - * Returns the current Post's category names. - * - * @since 3.11.0 - */ - const categoryNames = useMemo( () => { - return categories ? categories.map( ( c ) => c.name ) : []; - }, [ categories ] ); - - /** - * Returns the current Post's author names. - * - * @since 3.11.0 - */ - const authorNames = useMemo( () => { - return authors ? authors.map( ( a ) => a.name ) : []; - }, [ authors ] ); - - /** - * Saves the settings into the WordPress database whenever a setting change - * occurs. - * - * @since 3.13.0 - */ - useSaveSettings( 'editor-sidebar-settings', settings ); - - useEffect( () => { - setPostData( { - authors: authorNames, - tags: tagNames, - categories: categoryNames, - } ); - }, [ authorNames, tagNames, categoryNames ] ); + const { settings, setSettings } = useSettings(); /** * Track sidebar opening. @@ -301,163 +204,52 @@ const ContentHelperEditorSidebar = (): JSX.Element => { } }; - /** - * Track sidebar settings change. - * - * @since 3.12.0 - * - * @param {string} filter The filter name. - * @param {Object} props The filter properties. - */ - const trackSettingsChange = ( filter: string, props: object ): void => { - Telemetry.trackEvent( 'editor_sidebar_settings_changed', { filter, ...props } ); - }; - - /** - * Returns the settings pane of the Content Helper Sidebar. - * - * @since 3.11.0 - * - * @return {JSX.Element} The settings pane of the Content Helper Sidebar. - */ - const Settings = (): JSX.Element => { - return ( - <> - { - if ( isInEnum( selection, Period ) ) { - setSettings( { - ...settings, - SettingsPeriod: selection as Period, - } ); - trackSettingsChange( 'period', { period: selection } ); - } - } } - value={ settings.SettingsPeriod } - > - { - Object.values( Period ).map( ( value ) => - - ) - } - - { - if ( isInEnum( selection, Metric ) ) { - setSettings( { - ...settings, - SettingsMetric: selection as Metric, - } ); - trackSettingsChange( 'metric', { metric: selection } ); - } - } } - value={ settings.SettingsMetric } - > - { - Object.values( Metric ).map( ( value ) => - - ) - } - - - ); - }; - return ( - } + } name="wp-parsely-content-helper" className="wp-parsely-content-helper" - title={ __( 'Parse.ly Editor Sidebar', 'wp-parsely' ) } + title={ __( 'Parse.ly', 'wp-parsely' ) } > - - { - setSettings( { ...settings, SettingsOpen: next } ); - trackToggle( 'settings', next ); - } } - > - - - - - { - setSettings( { - ...settings, PerformanceDetailsOpen: next, - } ); - trackToggle( 'performance_details', next ); - } } - > - { - - - - } - - - - { - setSettings( { - ...settings, RelatedTopPostsOpen: next, - } ); - trackToggle( 'related_top_posts', next ); - } } - > - { - - - - } - - - - } - title={ __( 'Title Suggestions', 'wp-parsely' ) } - initialOpen={ settings.TitleSuggestionsOpen } - onToggle={ ( next ) => { - setSettings( { - ...settings, TitleSuggestionsOpen: next, - } ); - trackToggle( 'title_suggestions', next ); - } } - > - { - - - - } - - + + + , + name: 'tools', + title: __( 'Tools', 'wp-parsely' ), + }, + { + icon: ChartIcon, + name: 'performance', + title: __( 'Performance', 'wp-parsely' ), + }, + ] } + onSelect={ ( tabName ) => { + setSettings( { ...settings, InitialTabName: tabName } ); + Telemetry.trackEvent( 'editor_sidebar_tab_selected', { tab: tabName } ); + } } + > + { ( tab ) => ( + <> + { tab.name === 'tools' && ( + + ) } + { tab.name === 'performance' && ( + + ) } + + ) } + + + ); }; @@ -465,5 +257,15 @@ const ContentHelperEditorSidebar = (): JSX.Element => { // Registering Plugin to WordPress Block Editor. registerPlugin( BLOCK_PLUGIN_ID, { icon: LeafIcon, - render: ContentHelperEditorSidebar, + render: () => ( + + + + ), } ); + +// Initialize Smart Linking. +initSmartLinking(); diff --git a/src/content-helper/editor-sidebar/performance-details/component-panel-categories.tsx b/src/content-helper/editor-sidebar/performance-details/component-panel-categories.tsx new file mode 100644 index 000000000..e2be1f53f --- /dev/null +++ b/src/content-helper/editor-sidebar/performance-details/component-panel-categories.tsx @@ -0,0 +1,136 @@ +/** + * WordPress dependencies + */ +import { SelectControl, Spinner, Tooltip } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { getMetricDescription, isInEnum, Metric } from '../../common/utils/constants'; +import { formatToImpreciseNumber } from '../../common/utils/number'; +import { PerformanceStatPanel } from './component-panel'; +import { PerformanceData } from './model'; + +/** + * PerformanceCategoriesPanel component props. + * + * @since 3.14.0 + */ +type PerformanceCategoriesPanelProps = { + data: PerformanceData; + isLoading: boolean; +} + +/** + * The PerformanceCategoriesPanel component. + * + * Renders the Categories panel in the Performance tab. + * + * @since 3.14.0 + * + * @param { PerformanceCategoriesPanelProps } props The component's props. + * + * @return { JSX.Element } The PerformanceCategoriesPanel JSX Element. + */ +export const PerformanceCategoriesPanel = ( { + data, + isLoading, +}: Readonly ): JSX.Element => { + const [ metric, setMetric ] = useState( Metric.Views ); + const [ isOpen, setIsOpen ] = useState( false ); + + // Remove unneeded totals to simplify upcoming map() calls. + if ( ! isLoading ) { + delete data.referrers.types[ 'totals' as unknown as number ]; + } + + // Returns an internationalized referrer title based on the passed key. + const getKeyTitle = ( key: string ): string => { + switch ( key ) { + case 'social': return __( 'Social', 'wp-parsely' ); + case 'search': return __( 'Search', 'wp-parsely' ); + case 'other': return __( 'Other', 'wp-parsely' ); + case 'internal': return __( 'Internal', 'wp-parsely' ); + case 'direct': return __( 'Direct', 'wp-parsely' ); + } + + return key; + }; + + /* translators: %s: metric description */ + const subtitle = sprintf( __( 'By %s', 'wp-parsely' ), getMetricDescription( metric ) ); + return ( + setIsOpen( ! isOpen ) } + > + { isOpen && ( +
+ { + if ( isInEnum( selection, Metric ) ) { + setMetric( selection as Metric ); + } + } } + > + { Object.values( Metric ).map( ( value ) => ( + + ) ) } + +
+ ) } + { isLoading ? ( +
+ +
+ ) : ( +
+
+ { Object.entries( data.referrers.types ).map( ( [ key, value ] ) => { + const ariaLabel = sprintf( + /* translators: 1: Referrer type, 2: Percentage value, %%: Escaped percent sign */ + __( '%1$s: %2$s%%', 'wp-parsely' ), + getKeyTitle( key ), + value.viewsPercentage, + ); + + return ( + +
+
+ ); + } ) } +
+
+ { Object.entries( data.referrers.types ).map( ( [ key, value ] ) => ( +
+
+
{ getKeyTitle( key ) }
+
{ formatToImpreciseNumber( value.views ) }
+
+ ) ) } +
+
+ ) } +
+ ); +}; diff --git a/src/content-helper/editor-sidebar/performance-details/component-panel-overview.tsx b/src/content-helper/editor-sidebar/performance-details/component-panel-overview.tsx new file mode 100644 index 000000000..a552c54df --- /dev/null +++ b/src/content-helper/editor-sidebar/performance-details/component-panel-overview.tsx @@ -0,0 +1,310 @@ +/** + * WordPress dependencies + */ +import { MenuGroup, MenuItem, Spinner } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { + Icon, + check, + moreHorizontal, + people, + reset, + rotateLeft, + seen, +} from '@wordpress/icons'; +import { Telemetry } from '../../../js/telemetry/telemetry'; + +/** + * Internal dependencies + */ +import { ClockIcon } from '../../common/icons/clock-icon'; +import { SidebarSettings, useSettings } from '../../common/settings'; +import { formatToImpreciseNumber } from '../../common/utils/number'; +import { PerformanceStatPanel } from './component-panel'; +import { PerformanceData } from './model'; + +/** + * List of available data points to display in the Overview panel. + * + * @since 3.14.0 + */ +const availableDataPoints = [ + { + name: 'views', + title: __( 'Page Views', 'wp-parsely' ), + icon: seen, + }, + { + name: 'visitors', + title: __( 'Visitors', 'wp-parsely' ), + icon: people, + }, + { + name: 'avgEngaged', + title: __( 'Avg. Engaged', 'wp-parsely' ), + icon: , + }, + { + name: 'recirculation', + title: __( 'Recirculation', 'wp-parsely' ), + icon: rotateLeft, + smallText: true, + }, +]; + +/** + * Checks if a data point is visible in the sidebar settings. + * + * @since 3.14.0 + * + * @param { SidebarSettings } settings The sidebar settings. + * @param { string } name The name of the data point. + * + * @return { boolean } Whether the data point is visible. + */ +const isDataPointVisible = ( settings: SidebarSettings, name: string ): boolean => { + return settings.PerformanceStatsSettings.VisibleDataPoints.includes( name ); +}; + +/** + * Props for the DataPoint component. + * + * @since 3.14.0 + */ +type DataPointProps = { + title: string; + value: string; + icon: JSX.Element; + smallText?: boolean; + isVisible?: boolean; +} + +/** + * A single data point to display in the Overview panel. + * + * @since 3.14.0 + * + * @param { DataPointProps } props The component's props. + * + * @return { JSX.Element | null } The DataPoint JSX Element, or null if it's not visible. + */ +const DataPoint = ( + { title, value, icon, smallText, isVisible = true }: Readonly +): JSX.Element | null => { + if ( ! isVisible ) { + return null; + } + + return ( +
+ + { title } + { value } +
+ ); +}; + +/** + * Props for the PerformanceDataPoints component. + * + * @since 3.14.0 + */ +type PerformanceDataPointsProp = { + dataPoints: DataPointProps[] +} + +/** + * A grid of data points to display in the Overview panel. + * + * @since 3.14.0 + * + * @param { PerformanceDataPointsProp } props The component's props. + */ +const PerformanceDataPoints = ( + { dataPoints }: Readonly +): JSX.Element => { + return ( +
+ { dataPoints.map( ( { title, value, icon, smallText, isVisible } ) => ( + + ) ) } +
+ ); +}; + +/** + * Props for the OverviewMenu component. + * + * @since 3.14.0 + */ +type OverviewMenuProps = { + onClose: () => void; +} + +/** + * A dropdown menu for the Overview panel. + * + * @since 3.14.0 + * + * @param { OverviewMenuProps } props The component's props. + */ +const OverviewMenu = ( + { onClose }: Readonly +): JSX.Element => { + const { settings, setSettings } = useSettings(); + + /** + * Toggles a data point's visibility in the sidebar settings. + * + * @since 3.14.0 + * + * @param { string } dataPoint The name of the data point. + */ + const toggleDataPoint = ( dataPoint: string ): void => { + // Check if the dataPoint is in the settings.PerformanceStatsSettings.VisibleDataPoints array + // If it is, remove it with setSettings, if not, add it. + if ( isDataPointVisible( settings, dataPoint ) ) { + setSettings( { + PerformanceStatsSettings: { + ...settings.PerformanceStatsSettings, + VisibleDataPoints: settings.PerformanceStatsSettings.VisibleDataPoints.filter( + ( p ) => p !== dataPoint + ), + }, + } ); + Telemetry.trackEvent( 'editor_sidebar_performance_datapoint_hidden', { dataPoint } ); + } else { + setSettings( { + PerformanceStatsSettings: { + ...settings.PerformanceStatsSettings, + VisibleDataPoints: [ ...settings.PerformanceStatsSettings.VisibleDataPoints, dataPoint ], + }, + } ); + Telemetry.trackEvent( 'editor_sidebar_performance_datapoint_shown', { dataPoint } ); + } + }; + + /** + * Handles a click on a menu item. + * + * @since 3.14.0 + * + * @param { string } selection + */ + const onClick = ( selection: string ): void => { + toggleDataPoint( selection ); + onClose(); + }; + + /** + * Resets all data points to their default visibility. + * + * @since 3.14.0 + */ + const resetAll = (): void => { + setSettings( { + PerformanceStatsSettings: { + ...settings.PerformanceStatsSettings, + VisibleDataPoints: [ 'views', 'visitors', 'avgEngaged', 'recirculation' ], + }, + } ); + Telemetry.trackEvent( 'editor_sidebar_performance_datapoints_reset' ); + onClose(); + }; + + return ( + <> + + { availableDataPoints.map( ( value ) => ( + onClick( value.name ) } + > + + { value.title } + + ) ) } + + + + { __( 'Reset all', 'wp-parsely' ) } + + + + ); +}; + +/** + * Props for the PerformanceOverviewPanel component. + * + * @since 3.14.0 + */ +type PerformanceOverviewPanelProps = { + data: PerformanceData; + isLoading?: boolean; +} + +/** + * The Overview panel for the Performance Stats Sidebar. + * + * @since 3.14.0 + * + * @param { PerformanceOverviewPanelProps } props The component's props. + */ +export const PerformanceOverviewPanel = ( { + data, + isLoading = false, +}: Readonly ): JSX.Element => { + const { settings } = useSettings(); + + let dataPointsWithValues: DataPointProps[] = []; + if ( ! isLoading ) { + dataPointsWithValues = availableDataPoints.map( ( dataPoint ) => { + let value; + switch ( dataPoint.name ) { + case 'views': + value = formatToImpreciseNumber( data.views ); + break; + case 'visitors': + value = formatToImpreciseNumber( data.visitors ); + break; + case 'avgEngaged': + value = data.avgEngaged; + break; + default: + value = __( 'Coming soon!', 'wp-parsely' ); + break; + } + return { + ...dataPoint, + value, + isVisible: isDataPointVisible( settings, dataPoint.name ), + }; + } ); + } + + return ( + } + > + { ( isLoading ? ( +
+ +
+ ) : ( + + ) ) } +
+ ); +}; diff --git a/src/content-helper/editor-sidebar/performance-details/component-panel-referrers.tsx b/src/content-helper/editor-sidebar/performance-details/component-panel-referrers.tsx new file mode 100644 index 000000000..c0120ed13 --- /dev/null +++ b/src/content-helper/editor-sidebar/performance-details/component-panel-referrers.tsx @@ -0,0 +1,112 @@ +/** + * WordPress dependencies + */ +import { SelectControl, Spinner } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { getMetricDescription, isInEnum, Metric } from '../../common/utils/constants'; +import { PerformanceStatPanel } from './component-panel'; +import { PerformanceData } from './model'; + +/** + * PerformanceReferrersPanel component props. + * + * @since 3.14.0 + */ +type PerformanceReferrersPanelProps = { + data: PerformanceData; + isLoading: boolean; +} + +/** + * The Referrers panel for the Performance Stats Sidebar. + * + * @since 3.14.0 + * + * @param { PerformanceReferrersPanelProps } props The component's props. + * + * @return { JSX.Element } The PerformanceReferrersPanel JSX Element. + */ +export const PerformanceReferrersPanel = ( { + data, + isLoading, +}: Readonly ): JSX.Element => { + const [ metric, setMetric ] = useState( Metric.Views ); + const [ isOpen, setIsOpen ] = useState( false ); + + /* translators: %s: metric description */ + const subtitle = sprintf( __( 'By %s', 'wp-parsely' ), getMetricDescription( metric ) ); + return ( + setIsOpen( ! isOpen ) } + > + { isOpen && ( +
+ { + if ( isInEnum( selection, Metric ) ) { + setMetric( selection as Metric ); + } + } } + > + { Object.values( Metric ).map( ( value ) => ( + + ) ) } + +
+ ) } + { isLoading ? ( +
+ +
+ ) : ( +
+ { Object.entries( data.referrers.top ).map( ( [ key, value ] ) => { + if ( key === 'totals' ) { + return null; + } + + let referrerUrl = key; + if ( key === 'direct' ) { + referrerUrl = __( 'Direct', 'wp-parsely' ); + } + + /* translators: %s: Percentage value, %%: Escaped percent sign */ + const ariaLabel = sprintf( __( '%s%%', 'wp-parsely' ), value.viewsPercentage ); // eslint-disable-line @wordpress/valid-sprintf + + return ( +
+
+ { referrerUrl } +
+
+
+
+
+ { value.views } +
+
+ ); + } ) } +
+ ) } +
+ ); +}; diff --git a/src/content-helper/editor-sidebar/performance-details/component-panel.tsx b/src/content-helper/editor-sidebar/performance-details/component-panel.tsx new file mode 100644 index 000000000..6ee2f785b --- /dev/null +++ b/src/content-helper/editor-sidebar/performance-details/component-panel.tsx @@ -0,0 +1,104 @@ +/** + * WordPress dependencies + */ +import { + Button, + DropdownMenu, + __experimentalHStack as HStack, + __experimentalHeading as Heading, + Spinner, +} from '@wordpress/components'; +import type { ReactNode } from 'react'; + +/** + * PerformanceStatPanel component props. + * + * @since 3.14.0 + */ +type PerformanceStatPanelProps = { + title: string; + icon?: JSX.Element; + subtitle?: string; + level?: Parameters[0]['level']; + children: ReactNode; + controls?: Parameters[0]['controls']; + dropdownChildren?: Parameters[0]['children']; + onClick?: () => void; + isOpen?: boolean; + isLoading?: boolean; +} + +/** + * The PerformanceStatPanel component. + * This component is the raw panel used to display performance stats. + * + * If `dropdownChildren` is set, it will be used as the DropdownMenu. + * if `controls` is set, it will be used to render the DropdownMenu. + * + * @since 3.14.0 + * + * @param { PerformanceStatPanelProps } props The component's props. + * + * @return { JSX.Element } The PerformanceStatPanel JSX Element. + */ +export const PerformanceStatPanel = ( + { title, + icon, + subtitle, + level = 2, + children, + controls, + onClick, + isOpen, + isLoading, + dropdownChildren }: Readonly +): JSX.Element => { + return ( +
+ + { title } + { subtitle && ! isOpen && + { subtitle } + } + { ( controls && ! dropdownChildren ) && ( + + ) } + { dropdownChildren && ( + + ) } + { icon && ! dropdownChildren && ! controls && ( +
+ ); +}; diff --git a/src/content-helper/editor-sidebar/performance-details/component.tsx b/src/content-helper/editor-sidebar/performance-details/component.tsx index 96119f287..f8c78b604 100644 --- a/src/content-helper/editor-sidebar/performance-details/component.tsx +++ b/src/content-helper/editor-sidebar/performance-details/component.tsx @@ -1,50 +1,215 @@ /** * WordPress dependencies */ -import { Button, Spinner } from '@wordpress/components'; +import { + __experimentalInputControlPrefixWrapper as InputControlPrefixWrapper, + Button, + MenuGroup, + MenuItem, + SelectControl, +} from '@wordpress/components'; import { useEffect, useState } from '@wordpress/element'; -import { __, _n, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; +import { + check, + moreVertical, + reset, +} from '@wordpress/icons'; +import { Telemetry } from '../../../js/telemetry/telemetry'; /** * Internal dependencies */ import { ContentHelperError } from '../../common/content-helper-error'; -import { Period, getPeriodDescription } from '../../common/utils/constants'; -import { formatToImpreciseNumber } from '../../common/utils/number'; +import { SidebarSettings, useSettings } from '../../common/settings'; +import { + Period, + getPeriodDescription, + isInEnum, +} from '../../common/utils/constants'; +import { PerformanceStatPanel } from './component-panel'; +import { PerformanceCategoriesPanel } from './component-panel-categories'; +import { PerformanceOverviewPanel } from './component-panel-overview'; +import { PerformanceReferrersPanel } from './component-panel-referrers'; import { PerformanceData } from './model'; import { PerformanceDetailsProvider } from './provider'; +import './performance-details.scss'; // Number of attempts to fetch the data before displaying an error. const FETCH_RETRIES = 1; /** - * Defines the props structure for PerformanceDetails. + * Panel metadata descriptor. * - * @since 3.11.0 + * @since 3.14.0 */ -interface PerformanceDetailsProps { - period: Period; -} +type PanelDescriptor = { + name: string; + label: string; + forced?: boolean; +}; + +/** + * List of available panels to display in the Performance Stats menu. + * + * @since 3.14.0 + */ +const availablePanels: PanelDescriptor[] = [ + { + name: 'overview', + label: __( 'Overview', 'wp-parsely' ), + }, + { + name: 'categories', + label: __( 'Referrer Categories', 'wp-parsely' ), + }, + { + name: 'referrers', + label: __( 'Referrers', 'wp-parsely' ), + }, +]; + +/** + * Checks if a panel is visible in the sidebar settings. + * + * @since 3.14.0 + * + * @param { SidebarSettings } settings The sidebar settings. + * @param { string } panel The name of the panel. + * + * @return { boolean } True if the panel is visible, false otherwise. + */ +const isPanelVisible = ( settings: SidebarSettings, panel: string ): boolean => { + return settings.PerformanceStatsSettings.VisiblePanels.includes( panel ); +}; + +/** + * PerformanceStatsMenu dropdown menu component. + * + * @since 3.14.0 + * + * @param { Function } onClose Callback to close the dropdown menu. + * + * @return { JSX.Element } The dropdown menu JSX Element. + */ +const PerformanceStatsMenu = ( + { onClose }: { onClose: () => void } +): JSX.Element => { + const { settings, setSettings } = useSettings(); + + /** + * Toggles a panel's visibility in the sidebar settings. + * If the panel is forced, it will not be toggled. + * + * @since 3.14.0 + * + * @param { string } panel The name of the panel to toggle. + */ + const togglePanel = ( panel: string ): void => { + // Do not toggle panels that are forced to be visible. + if ( availablePanels.find( ( p ) => p.name === panel )?.forced ) { + return; + } + + // Check if the panel is in the settings.PerformanceStatsSettings.VisiblePanels array + // If it is, remove it with setSettings, if not, add it. + if ( isPanelVisible( settings, panel ) ) { + setSettings( { + PerformanceStatsSettings: { + ...settings.PerformanceStatsSettings, + VisiblePanels: settings.PerformanceStatsSettings.VisiblePanels.filter( + ( p ) => p !== panel + ), + }, + } ); + Telemetry.trackEvent( 'editor_sidebar_performance_panel_closed', { panel } ); + } else { + setSettings( { + PerformanceStatsSettings: { + ...settings.PerformanceStatsSettings, + VisiblePanels: [ ...settings.PerformanceStatsSettings.VisiblePanels, panel ], + }, + } ); + Telemetry.trackEvent( 'editor_sidebar_performance_panel_opened', { panel } ); + } + }; + + /** + * Handles the click event on a menu item. + * + * @since 3.14.0 + * + * @param { string } selection The name of the selected panel. + */ + const onClick = ( selection: string ): void => { + togglePanel( selection ); + onClose(); + }; + + /** + * Resets all panels to their default visibility. + * + * @since 3.14.0 + */ + const resetAll = (): void => { + setSettings( { + PerformanceStatsSettings: { + ...settings.PerformanceStatsSettings, + VisiblePanels: availablePanels.map( ( panel ) => panel.name ), + }, + } ); + Telemetry.trackEvent( 'editor_sidebar_performance_panel_reset' ); + onClose(); + }; + + return ( + <> + + { availablePanels.map( ( item ) => ( + onClick( item.name ) } + > + { item.label } + + ) ) } + + + + { __( 'Reset all', 'wp-parsely' ) } + + + + ); +}; /** - * Specifies props structure for PerformanceDetailsSections. + * PerformanceStats component properties. + * + * @since 3.14.0 */ -interface PerformanceSectionProps extends PerformanceDetailsProps { - data: PerformanceData; +type PerformanceStatsProps = { + period: Period; } /** - * Outputs the current post's details or shows an error message on failure. + * PerformanceStats component. * - * @param {PerformanceDetailsProps} props The component's props. + * @since 3.14.0 + * + * @param { PerformanceStatsProps } props The component's properties. */ -export function PerformanceDetails( - { period }: Readonly -): JSX.Element { +export const PerformanceStats = ( + { period }: PerformanceStatsProps +): JSX.Element => { const [ loading, setLoading ] = useState( true ); const [ error, setError ] = useState(); const [ postDetails, setPostDetails ] = useState(); + const { settings, setSettings } = useSettings(); + useEffect( () => { const provider = new PerformanceDetailsProvider(); @@ -73,233 +238,84 @@ export function PerformanceDetails( }; }, [ period ] ); - if ( error ) { - return error.Message(); - } - - return ( - loading - ? ( -
- -
- ) - : ( - - ) - ); -} - -/** - * Outputs all the "Current Post Details" sections. - * - * @param {PerformanceSectionProps} props The props needed to populate the sections. - */ -function PerformanceDetailsSections( - props: Readonly -): JSX.Element { - return ( -
-
- { getPeriodDescription( props.period ) } -
- - - - -
- ); -} - -/** - * Outputs the "General Performance" (Views, Visitors, Time) section. - * - * @param {PerformanceSectionProps} props The props needed to populate the section. - */ -function GeneralPerformanceSection( - props: Readonly -): JSX.Element { - const data = props.data; - - return ( -
- - - - - - - - - - - - - - - -
{ formatToImpreciseNumber( data.views ) }{ formatToImpreciseNumber( data.visitors ) }{ data.avgEngaged }
{ __( 'Page Views', 'wp-parsely' ) }{ __( 'Visitors', 'wp-parsely' ) }{ __( 'Avg. Time', 'wp-parsely' ) }
-
- ); -} - -/** - * Outputs the "Referrer Types" section. - * - * @param {PerformanceSectionProps} props The props needed to populate the section. - */ -function ReferrerTypesSection( - props: Readonly -): JSX.Element { - const data = props.data; - - // Remove unneeded totals to simplify upcoming map() calls. - delete data.referrers.types[ 'totals' as unknown as number ]; - - // Returns an internationalized referrer title based on the passed key. - const getKeyTitle = ( key: string ): string => { - switch ( key ) { - case 'social': return __( 'Social', 'wp-parsely' ); - case 'search': return __( 'Search', 'wp-parsely' ); - case 'other': return __( 'Other', 'wp-parsely' ); - case 'internal': return __( 'Internal', 'wp-parsely' ); - case 'direct': return __( 'Direct', 'wp-parsely' ); - } - - return key; - }; - - return ( -
-
{ __( 'Referrers (Page Views)', 'wp-parsely' ) }
- -
{ - Object.entries( data.referrers.types ).map( ( [ key, value ] ) => { - const ariaLabel = sprintf( - /* translators: 1: Referrer type, 2: Percentage value, %%: Escaped percent sign */ - __( '%1$s: %2$s%%', 'wp-parsely' ), - getKeyTitle( key ), value.viewsPercentage - ); - - return ( -
-
- ); - } ) } -
- - - - { - Object.keys( data.referrers.types ).map( ( key ) => { - return ; - } ) } - - - - { - Object.entries( data.referrers.types ).map( ( [ key, value ] ) => { - return ; - } ) } - - -
{ getKeyTitle( key ) }
{ formatToImpreciseNumber( value.views ) }
-
- ); -} - -/** - * Outputs the "Top Referrers" section. - * - * @param {PerformanceSectionProps} props The props needed to populate the section. - */ -function TopReferrersSection( - props: Readonly -): JSX.Element { - const data = props.data; - let totalViewsPercentage = 0; - return ( -
- - - - - - - - { - Object.entries( data.referrers.top ).map( ( [ key, value ] ) => { - if ( key === 'totals' ) { - totalViewsPercentage = Number( value.viewsPercentage ); - return null; +
+ } + > +
+ + { __( 'Period: ', 'wp-parsely' ) } + } + onChange={ ( selection ) => { + if ( isInEnum( selection, Period ) ) { + setSettings( { + PerformanceStatsSettings: { + ...settings.PerformanceStatsSettings, + Period: selection as Period, + }, + } ); + Telemetry.trackEvent( + 'editor_sidebar_performance_period_changed', + { period: selection } + ); + } + } } + > + { Object.values( Period ).map( ( value ) => ( + + ) ) } + +
+
- let referrerUrl = key; - if ( key === 'direct' ) { - referrerUrl = __( 'Direct', 'wp-parsely' ); - } - - /* translators: %s: Percentage value, %%: Escaped percent sign */ - const ariaLabel = sprintf( __( '%s%%', 'wp-parsely' ), value.viewsPercentage ); // eslint-disable-line @wordpress/valid-sprintf - - return ( -
- - - - - ); - } ) - } - -
{ __( 'Top Referrers', 'wp-parsely' ) }{ __( 'Page Views', 'wp-parsely' ) }
{ referrerUrl } -
-
-
{ formatToImpreciseNumber( value.views ) }
-
{ - /* translators: %s: Percentage value, %%: Escaped percent sign */ - sprintf( _n( // eslint-disable-line @wordpress/valid-sprintf - '%s%% of views came from top referrers.', - '%s%% of views came from top referrers.', - totalViewsPercentage, 'wp-parsely' ), totalViewsPercentage - ) - } -
-
- ); -} - -/** - * Outputs the "Actions" section. - * - * @param {PerformanceSectionProps} props The props needed to populate the section. - */ -function ActionsSection( - props: Readonly -): JSX.Element { - const data = props.data; - const ariaOpensNewTab = { - __( '(opens in new tab)', 'wp-parsely' ) } - ; - - return ( -
- - + { error ? ( + error.Message() + ) : ( + <> + { isPanelVisible( settings, 'overview' ) && ( + + ) } + { isPanelVisible( settings, 'categories' ) && ( + + ) } + { isPanelVisible( settings, 'referrers' ) && ( + + ) } + + ) } + { window.wpParselyPostUrl && ( + + ) }
); -} +}; diff --git a/src/content-helper/editor-sidebar/performance-details/performance-details.scss b/src/content-helper/editor-sidebar/performance-details/performance-details.scss new file mode 100644 index 000000000..e657cca8b --- /dev/null +++ b/src/content-helper/editor-sidebar/performance-details/performance-details.scss @@ -0,0 +1,344 @@ +@import "../../common/css/variables"; +@import "../../common/css/functions"; + +.wp-parsely-content-helper .wp-parsely-performance-panel { + padding: calc(16px); + border-top: 1px solid rgb(221, 221, 221); + margin-top: -1px; + + + /** View in Parse.ly button */ + .components-button.wp-parsely-view-post { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + } + + .panel-body { + width: 100%; + } + + .performance-stat-panel { + display: flex; + padding: to_rem(6px) 0 to_rem(16px) 0; + flex-direction: column; + align-items: flex-start; + gap: to_rem(8px); + align-self: stretch; + + .panel-header { + display: flex; + height: to_rem(16px); + width: 100%; + align-items: center; + gap: to_rem(6px); + margin-bottom: 0; + + /** Level 2 heading */ + &.level-2 { + margin-bottom: to_rem(15px); + + h2 { + margin-bottom: 0; + } + } + + /** Level 3 heading */ + &.level-3 { + + h3 { + margin-bottom: 0; + line-height: to_rem(16px); + font-size: var(--font-size--smaller, to_rem(11px)); + font-style: normal; + font-weight: 600; + text-transform: uppercase; + } + } + } + + .panel-subtitle { + display: flex; + flex: 1 0 0; + height: to_rem(16px); + align-items: center; + gap: to_rem(6px); + align-self: stretch; + color: var(--Gutenberg-Gray-700, #757575); + + /* Label */ + font-size: var(--font-size--smaller, to_rem(11px)); + font-style: normal; + font-weight: 600; + line-height: to_rem(16px); /* 145.455% */ + text-transform: uppercase; + } + } + + .components-heading { + margin: 0; + font-weight: 500; + line-height: normal; + display: block; + } + + .components-dropdown-menu { + line-height: 0; + } + + .performance-data-points { + display: flex; + align-items: flex-start; + align-content: flex-start; + gap: to_rem(16px) var(--grid-unit-20, to_rem(16px)); + + align-self: stretch; + flex-wrap: wrap; + + .data-point { + display: flex; + min-width: 100px; + padding: var(--grid-unit-20, to_rem(16px)) var(--grid-unit-10, to_rem(8px)); + flex-direction: column; + justify-content: center; + align-items: center; + gap: var(--grid-unit-05, to_rem(4px)); + flex: 1 0 0; + + border-radius: to_rem(4px); + background: var(--sidebar-white, #f0f0f0); + + svg { + display: flex; + align-items: flex-start; + border-radius: to_rem(2px); + } + + .data-point-title { + align-self: stretch; + color: var(--sidebar-black, #1e1e1e); + text-align: center; + font-size: to_rem(12px); + font-style: normal; + font-weight: 400; + line-height: to_rem(16px); /* 133.333% */ + } + + .data-point-value { + align-self: stretch; + color: var(--sidebar-black, #1e1e1e); + text-align: center; + font-size: to_rem(20px); + font-style: normal; + font-weight: 590; + line-height: to_rem(28px); /* 140% */ + + &.is-small { + font-size: var(--font-size--medium, to_rem(14px)); + } + } + } + } + + + // Multi-percentage bar. + div.multi-percentage-bar { + position: relative; + display: flex; + height: to_rem(16px); + align-items: flex-start; + align-self: stretch; + margin: to_rem(16px) 0; + + .bar-fill { + --radius: 2px; + position: relative; + align-self: stretch; + transition: filter 0.1s ease, transform 0.1s ease; + + &:hover { + filter: opacity(1) !important; + transform: scaleX(1) scaleY(1) !important; + } + + // Border radiuses for first and last bar-fills. + &:first-child { + border-radius: var(--radius) 0 0 var(--radius); + } + + &:last-child { + border-radius: 0 var(--radius) var(--radius) 0; + } + + // Bar-fill colors by referrer type. + &.direct { + background-color: hsl(var(--ref-direct)); + } + + &.internal { + background-color: hsl(var(--ref-internal)); + } + + &.search { + background-color: hsl(var(--ref-search)); + } + + &.social { + background-color: hsl(var(--ref-social)); + } + + &.other { + background-color: hsl(var(--ref-other)); + } + } + + &:hover .bar-fill { + filter: opacity(0.5); + transform: scaleX(1) scaleY(0.7); + } + } + + .percentage-bar-labels { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--grid-unit-20, to_rem(16px)); + align-self: stretch; + + .single-label { + display: flex; + justify-content: center; + align-items: center; + gap: to_rem(8px); + align-self: stretch; + + .label-color { + display: flex; + width: to_rem(3px); + height: to_rem(10px); + flex-direction: column; + justify-content: center; + align-items: center; + gap: to_rem(10px); + border-radius: to_rem(1.5px); + + &.direct { + background-color: hsl(var(--ref-direct)); + } + + &.internal { + background-color: hsl(var(--ref-internal)); + } + + &.search { + background-color: hsl(var(--ref-search)); + } + + &.social { + background-color: hsl(var(--ref-social)); + } + + &.other { + background-color: hsl(var(--ref-other)); + } + } + + .label-text { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + flex: 1 0 0; + overflow: hidden; + color: var(--sidebar-black, #1e1e1e); + font-feature-settings: "ss06" on; + text-overflow: ellipsis; + font-size: var(--font-size--small, to_rem(12px)); + font-style: normal; + font-weight: 400; + line-height: to_rem(16px); /* 133.333% */ + } + + .label-value { + color: var(--sidebar-black, #1e1e1e); + text-align: right; + font-size: var(--font-size--small, to_rem(12px)); + font-style: normal; + font-weight: 590; + line-height: to_rem(16px) /* 133.333% */ + } + } + } + + .referrers-list { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--grid-unit-20, to_rem(16px)); + align-self: stretch; + margin: to_rem(16px) 0; + + .referrers-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--grid-unit-10, to_rem(8px)); + align-self: stretch; + + .referrers-row-title { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + flex: 1; + overflow: hidden; + color: var(--sidebar-black, #1e1e1e); + font-feature-settings: "ss06" on; + text-overflow: ellipsis; + font-size: var(--font-size--small, to_rem(12px)); + font-style: normal; + font-weight: 400; + line-height: to_rem(16px); /* 133.333% */ + } + + .referrers-row-bar { + display: flex; + max-width: to_rem(100px); + flex-direction: column; + justify-content: center; + gap: to_rem(10px); + flex: 1; + + .percentage-bar { + // Bar background. + --radius: 1.5px; + background: var(--gray-400, #d7dbdf); + border-radius: var(--radius); + display: flex; + height: to_rem(3px); + margin: 0; + overflow: hidden; + + // Bar fill. + &::after { + background: var(--blueberry, #3858e9); + border-radius: var(--radius); + content: ""; + height: 100%; + width: var(--bar-fill); + } + } + } + + .referrers-row-value { + flex-shrink: 0; + min-width: to_rem(50px); + color: var(--sidebar-black, #1e1e1e); + text-align: right; + font-size: var(--font-size--small, to_rem(12px)); + font-style: normal; + font-weight: 590; + line-height: to_rem(16px); /* 133.333% */ + } + } + } +} diff --git a/src/content-helper/editor-sidebar/related-posts/component-filter-settings.tsx b/src/content-helper/editor-sidebar/related-posts/component-filter-settings.tsx new file mode 100644 index 000000000..eccc77371 --- /dev/null +++ b/src/content-helper/editor-sidebar/related-posts/component-filter-settings.tsx @@ -0,0 +1,197 @@ +/** + * WordPress dependencies + */ +import { + ComboboxControl, + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, +} from '@wordpress/components'; +import { ComboboxControlOption } from '@wordpress/components/build-types/combobox-control/types'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { PostFilter, PostFilterType } from '../../common/utils/constants'; +import { SidebarPostData } from '../editor-sidebar'; + +/** + * Defines the props structure for FilterTypes. + * + * @since 3.14.0 + */ +type FilterTypesProps = { + filter: PostFilter; + label?: string; + onFilterTypeChange: ( selection: string ) => void; + postData: SidebarPostData; +}; + +/** + * Returns the filter types ToggleGroupControl component. + * + * @since 3.14.0 + * + * @param {FilterTypesProps} props The component's props. + */ +const FilterTypes = ( + { filter, label, postData, ...props }: Readonly +): JSX.Element => { + return ( +
+ props.onFilterTypeChange( value as string ) } + isBlock + > + { postData.tags.length >= 1 && ( + + ) } + { postData.categories.length >= 1 && ( + + ) } + + +
+ ); +}; + +/** + * Defines the props structure for FilterValues. + * + * @since 3.14.0 + */ +type FilterValuesProps = { + filter: PostFilter; + label?: string; + onFilterValueChange: ( selection: string | null | undefined ) => void; + postData: SidebarPostData; +} + +/** + * Returns the filter values ComboboxControl component. + * + * @since 3.14.0 + * + * @param {FilterValuesProps} props The component's props. + */ +const FilterValues = ( { + filter, + postData, + ...props +}: Readonly ): JSX.Element => { + /** + * Returns the options that will populate the ComboboxControl. + * + * @since 3.11.0 + * + * @return {ComboboxControlOption[]} The resulting ComboboxControl options. + */ + const getOptions = (): ComboboxControlOption[] => { + if ( PostFilterType.Tag === filter.type ) { + return postData.tags.map( ( tag: string ) => ( { + value: tag, label: tag, + } ) ); + } + + if ( PostFilterType.Section === filter.type ) { + return postData.categories.map( ( section: string ) => ( { + value: section, label: section, + } ) ); + } + + if ( PostFilterType.Author === filter.type ) { + return postData.authors.map( ( author: string ) => ( { + value: author, label: author, + } ) ); + } + + return []; + }; + + return ( +
+ props.onFilterValueChange( selection ) } + options={ getOptions() } + value={ filter.value } + /> +
+ ); +}; + +/** + * Defines the props structure for FilterControls. + * + * @since 3.14.0 + */ +type FilterControlsProps = { + filter: PostFilter; + label?: string; + onFilterTypeChange: ( selection: string ) => void; + onFilterValueChange: ( selection: string | null | undefined ) => void; + postData: SidebarPostData; +} + +/** + * Returns the filter settings component. + * + * @since 3.14.0 + * + * @param {FilterControlsProps} props The component's props. + */ +export const RelatedPostsFilterSettings = ( { + filter, + postData, + label, + ...props +}: Readonly ): JSX.Element => { + /** + * Returns whether the filter values ComboboxControl should be displayed. + * + * @since 3.11.0 + * + * @return {boolean} Whether to display the filter values ComboboxControl. + */ + const shouldDisplayFilterValues = (): boolean => { + if ( + ( PostFilterType.Tag === filter.type && postData.tags.length > 1 ) || + ( PostFilterType.Section === filter.type && postData.categories.length > 1 ) || + ( PostFilterType.Author === filter.type && postData.authors.length > 1 ) + ) { + return true; + } + + return false; + }; + + return ( + <> + + { shouldDisplayFilterValues() && + + } + + ); +}; diff --git a/src/content-helper/editor-sidebar/related-posts/component-item.tsx b/src/content-helper/editor-sidebar/related-posts/component-item.tsx new file mode 100644 index 000000000..e7da64d2c --- /dev/null +++ b/src/content-helper/editor-sidebar/related-posts/component-item.tsx @@ -0,0 +1,107 @@ +/** + * WordPress dependencies + */ +import { Button, Dashicon, Rect, SVG, Tooltip } from '@wordpress/components'; +import { useDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { Icon, copySmall, link, seen } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { LeafIcon } from '../../common/icons/leaf-icon'; +import { PostListItemMetric, PostListItemProps } from '../../common/utils/post'; + +/** + * Returns a vertical divider. + * + * @since 3.14.0 + */ +const VerticalDivider = (): JSX.Element => { + return ( + + + + ); +}; + +/** + * Returns a single related post item. + * + * @since 3.14.0 + * + * @param { PostListItemProps } props The component's props. + */ +export const RelatedPostItem = ( + { metric, post, postContent }: Readonly +): JSX.Element => { + const { createNotice } = useDispatch( 'core/notices' ); + + let isLinked = false; + if ( postContent ) { + isLinked = postContent.includes( post.rawUrl ); + } + + return ( +
+ +
+
+
+
+ } + avgEngagedIcon={ } + /> +
+ { isLinked && ( +
+ + + +
+ ) } +
+ +
+
+
+
+
+ ); +}; diff --git a/src/content-helper/editor-sidebar/related-posts/component.tsx b/src/content-helper/editor-sidebar/related-posts/component.tsx new file mode 100644 index 000000000..125394e1c --- /dev/null +++ b/src/content-helper/editor-sidebar/related-posts/component.tsx @@ -0,0 +1,422 @@ +/** + * WordPress dependencies + */ +import { + __experimentalInputControlPrefixWrapper as InputControlPrefixWrapper, + SelectControl, +} from '@wordpress/components'; +import { useDebounce } from '@wordpress/compose'; +// eslint-disable-next-line import/named +import { store as coreStore, Taxonomy, User } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; +import { store as editorStore } from '@wordpress/editor'; +import { useEffect, useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { GutenbergFunction } from '../../../@types/gutenberg/types'; +import { Telemetry } from '../../../js/telemetry/telemetry'; +import { ContentHelperError } from '../../common/content-helper-error'; +import { SidebarSettings, useSettings } from '../../common/settings'; +import { + getMetricDescription, + getPeriodDescription, + isInEnum, + Metric, + Period, + PostFilter, + PostFilterType, +} from '../../common/utils/constants'; +import { PostData } from '../../common/utils/post'; +import { SidebarPostData } from '../editor-sidebar'; +import { RelatedPostsFilterSettings } from './component-filter-settings'; +import { RelatedPostItem } from './component-item'; +import { RelatedPostsProvider } from './provider'; +import './related-posts.scss'; + +const FETCH_RETRIES = 1; + +/** + * The Related Posts panel in the Editor Sidebar. + * + * @since 3.14.0 + */ +export const RelatedPostsPanel = (): JSX.Element => { + const { settings, setSettings } = useSettings(); + + const period = settings.RelatedPostsPeriod; + const metric = settings.RelatedPostsMetric; + + const [ postData, setPostData ] = useState( { + authors: [], categories: [], tags: [], + } ); + + /** + * Returns the current Post's ID, tags and categories. + * + * @since 3.11.0 + * @since 3.14.0 Moved from `editor-sidebar.tsx`. + */ + const { authors, categories, tags } = useSelect( ( select ) => { + const { getEditedPostAttribute } = select( editorStore ) as GutenbergFunction; + const { getEntityRecords } = select( coreStore ); + + const authorRecords: User[] | null = getEntityRecords( + 'root', 'user', { include: getEditedPostAttribute( 'author' ) } + ); + + const categoryRecords: Taxonomy[] | null = getEntityRecords( + 'taxonomy', 'category', { include: getEditedPostAttribute( 'categories' ) } + ); + + const tagRecords: Taxonomy[]|null = getEntityRecords( + 'taxonomy', 'post_tag', { include: getEditedPostAttribute( 'tags' ) } + ); + + return { + authors: authorRecords, + categories: categoryRecords, + tags: tagRecords, + }; + }, [] ); + + useEffect( () => { + // Set the post data only when all required properties have become + // available. + if ( authors && categories && tags ) { + setPostData( { + authors: authors.map( ( a ) => a.name ), + categories: categories.map( ( c ) => c.name ), + tags: tags.map( ( t ) => t.name ), + } ); + } + }, [ authors, categories, tags ] ); + + const [ loading, setLoading ] = useState( true ); + const [ error, setError ] = useState(); + const [ message, setMessage ] = useState(); + const [ posts, setPosts ] = useState( [] ); + const [ filter, setFilter ] = useState( + { + type: settings.RelatedPostsFilterBy as PostFilterType, + value: settings.RelatedPostsFilterValue, + } + ); + + const [ postContent, setPostContent ] = useState( undefined ); + const debouncedSetPostContent = useDebounce( setPostContent, 1000 ); + useSelect( ( select ) => { + const { getEditedPostContent } = select( 'core/editor' ) as GutenbergFunction; + debouncedSetPostContent( getEditedPostContent() ); + }, [ debouncedSetPostContent ] ); + + /** + * Updates all filter settings. + * + * @since 3.13.0 + * @since 3.14.0 Renamed from `handleRelatedPostsFilterChange` and + * moved from the editor sidebar to the related posts component. + * + * @param {PostFilterType} filterBy The new filter type. + * @param {string} value The new filter value. + */ + const onFilterChange = ( filterBy: PostFilterType, value: string ): void => { + setSettings( { + RelatedPostsFilterBy: filterBy, + RelatedPostsFilterValue: value, + } ); + }; + + /** + * Updates the metric setting. + * + * @since 3.14.0 + * + * @param {string} selection The new metric. + */ + const onMetricChange = ( selection: string ) => { + if ( isInEnum( selection, Metric ) ) { + setSettings( { + RelatedPostsMetric: selection as Metric, + } ); + Telemetry.trackEvent( 'related_posts_metric_changed', { metric: selection } ); + } + }; + + /** + * Updates the period setting. + * + * @since 3.14.0 + * + * @param {string} selection The new period. + */ + const onPeriodChange = ( selection: string ) => { + if ( isInEnum( selection, Period ) ) { + setSettings( { + RelatedPostsPeriod: selection as Period, + } ); + Telemetry.trackEvent( 'related_posts_period_changed', { period: selection } ); + } + }; + + /** + * Updates the filter type and sets its default value. + * + * @since 3.11.0 + * + * @param {string} newFilterType The new filter type. + */ + const updateFilterType = ( newFilterType: string ): void => { + if ( isInEnum( newFilterType, PostFilterType ) ) { + let value = ''; + const type = newFilterType as PostFilterType; + + if ( PostFilterType.Tag === type ) { + value = postData.tags[ 0 ]; + } + if ( PostFilterType.Section === type ) { + value = postData.categories[ 0 ]; + } + if ( PostFilterType.Author === type ) { + value = postData.authors[ 0 ]; + } + + if ( '' !== value ) { + onFilterChange( type, value ); + setFilter( { type, value } ); + Telemetry.trackEvent( 'related_posts_filter_type_changed', { filter_type: type } ); + } + } + }; + + useEffect( () => { + /** + * Returns whether the post data passed into this component is empty. + * + * @since 3.14.0 + * + * @return {boolean} Whether the post data is empty. + */ + const isPostDataEmpty = (): boolean => { + return Object.values( postData ).every( + ( value ) => 0 === value.length + ); + }; + + /** + * Returns the initial filter settings. + * + * The selection is based on whether the Post has tags or categories + * assigned to it. Otherwise, the filter is set to the first author. + * + * @since 3.11.0 + * + * @return {PostFilter} The initial filter settings. + */ + const getInitialFilterSettings = (): PostFilter => { + let value = ''; + let type = PostFilterType.Unavailable; + + if ( postData.tags.length >= 1 ) { + type = PostFilterType.Tag; + value = postData.tags[ 0 ]; + } else if ( postData.categories.length >= 1 ) { + type = PostFilterType.Section; + value = postData.categories[ 0 ]; + } else { + type = PostFilterType.Author; + value = postData.authors[ 0 ]; + } + + return { type, value }; + }; + + const fetchPosts = async ( retries: number ) => { + RelatedPostsProvider.getRelatedPosts( period, metric, filter ) + .then( ( result ): void => { + setPosts( result.posts ); + setMessage( result.message ); + setLoading( false ); + } ) + .catch( async ( err ) => { + if ( retries > 0 && err.retryFetch ) { + await new Promise( ( r ) => setTimeout( r, 500 ) ); + await fetchPosts( retries - 1 ); + } else { + setLoading( false ); + setError( err ); + } + } ); + }; + + const filterTypeIsTag = PostFilterType.Tag === filter.type; + const filterTypeIsSection = PostFilterType.Section === filter.type; + const filterTypeIsUnavailable = PostFilterType.Unavailable === filter.type; + const noTagsExist = 0 === postData.tags.length; + const noCategoriesExist = 0 === postData.categories.length; + const tagIsUnavailable = filterTypeIsTag && ! postData.tags.includes( filter.value ); + const sectionIsUnavailable = filterTypeIsSection && ! postData.categories.includes( filter.value ); + + setLoading( true ); + + if ( filterTypeIsUnavailable || ( filterTypeIsTag && noTagsExist ) || ( filterTypeIsSection && noCategoriesExist ) ) { + if ( ! isPostDataEmpty() ) { + setFilter( getInitialFilterSettings() ); + } + } else if ( tagIsUnavailable ) { + setFilter( { type: PostFilterType.Tag, value: postData.tags[ 0 ] } ); + } else if ( sectionIsUnavailable ) { + setFilter( { type: PostFilterType.Section, value: postData.categories[ 0 ] } ); + } else { + fetchPosts( FETCH_RETRIES ); + } + + return (): void => { + setLoading( false ); + setPosts( [] ); + setMessage( '' ); + setError( undefined ); + }; + }, [ period, metric, filter, postData ] ); + + /** + * Updates the filter value. + * + * @param {string} newFilterValue The new filter value. + * + * @since 3.11.0 + */ + const updateFilterValue = ( + newFilterValue: string | null | undefined + ): void => { + if ( typeof newFilterValue === 'string' ) { + onFilterChange( filter.type, newFilterValue ); + setFilter( { ...filter, value: newFilterValue } ); + } + }; + + /** + * Returns the top related posts message. + * + * If the filter is by Author: "Top related posts by [post_author] in the [period]." + * If the filter is by Section: "Top related posts in the “[section_name]” section in the [period]." + * If the filter is by Tag: "Top related posts with the “[wp_term name]” tag in the [period]." + * + * @since 3.14.0 + */ + const getTopRelatedPostsMessage = (): string => { + if ( PostFilterType.Tag === filter.type ) { + return sprintf( + /* translators: 1: tag name, 2: period */ + __( 'Top related posts with the “%1$s” tag in the %2$s.', 'wp-parsely' ), + filter.value, getPeriodDescription( period, true ) + ); + } + + if ( PostFilterType.Section === filter.type ) { + return sprintf( + /* translators: 1: section name, 2: period */ + __( 'Top related posts in the “%1$s” section in the %2$s.', 'wp-parsely' ), + filter.value, getPeriodDescription( period, true ) + ); + } + + if ( PostFilterType.Author === filter.type ) { + return sprintf( + /* translators: 1: author name, 2: period */ + __( 'Top related posts by %1$s in the %2$s.', 'wp-parsely' ), + filter.value, getPeriodDescription( period, true ) + ); + } + + // Fallback to the default message. + return message ?? ''; + }; + + return ( +
+
+ { __( 'Find top-performing related posts based on a key metric.', 'wp-parsely' ) } +
+
+
+ onMetricChange( value ) } + prefix={ + { __( 'Metric: ', 'wp-parsely' ) } + } + value={ metric } + > + { Object.values( Metric ).map( ( value ) => ( + + ) ) } + + { __( 'Period: ', 'wp-parsely' ) } + } + onChange={ ( selection ) => onPeriodChange( selection ) } + > + { Object.values( Period ).map( ( value ) => ( + + ) ) } + +
+
+ +
+ +
+
+

+ { getTopRelatedPostsMessage() } +

+
+ { error && ( + error.Message() + ) } + { loading && ( +
+ { __( 'Loading…', 'wp-parsely' ) } +
+ ) } + { ! loading && ! error && posts.length === 0 && ( +
+ { __( 'No related posts found.', 'wp-parsely' ) } +
+ ) } + { ! loading && posts.length > 0 && ( +
+ { posts.map( ( post: PostData ) => ( + + ) ) } +
+ ) } +
+
+
+ ); +}; diff --git a/src/content-helper/editor-sidebar/related-top-posts/provider.ts b/src/content-helper/editor-sidebar/related-posts/provider.ts similarity index 79% rename from src/content-helper/editor-sidebar/related-top-posts/provider.ts rename to src/content-helper/editor-sidebar/related-posts/provider.ts index 504010345..dda6cfde0 100644 --- a/src/content-helper/editor-sidebar/related-top-posts/provider.ts +++ b/src/content-helper/editor-sidebar/related-posts/provider.ts @@ -29,7 +29,7 @@ import { PostData } from '../../common/utils/post'; * The form of the query that gets posted to the analytics/posts WordPress REST * API endpoint. */ -interface RelatedTopPostsApiQuery { +interface RelatedPostsApiQuery { message: string; // Selected filter message to be displayed to the user. query: AnalyticsApiOptionalQueryParams } @@ -38,25 +38,25 @@ interface RelatedTopPostsApiQuery { * The form of the response returned by the /stats/posts WordPress REST API * endpoint. */ -interface RelatedTopPostsApiResponse { +interface RelatedPostsApiResponse { error?: Error; data?: PostData[]; } /** - * The form of the result returned by the getRelatedTopPosts() function. + * The form of the result returned by the getRelatedPosts() function. */ -export interface GetRelatedTopPostsResult { +export interface GetRelatedPostsResult { message: string; posts: PostData[]; } export const RELATED_POSTS_DEFAULT_LIMIT = 5; -export class RelatedTopPostsProvider { +export class RelatedPostsProvider { /** - * Returns related top posts to the one that is currently being edited - * within the WordPress Block Editor. + * Returns related posts to the one that is currently being edited within + * the WordPress Block Editor. * * The 'related' status is determined by the current post's Author, Category * or tag. @@ -65,15 +65,15 @@ export class RelatedTopPostsProvider { * @param {Metric} metric The metric to sort by. * @param {PostFilter} filter The selected filter type and value to use. * - * @return {Promise} Object containing message and posts. + * @return {Promise} Object containing message and posts. */ - static async getRelatedTopPosts( + static async getRelatedPosts( period: Period, metric: Metric, filter: PostFilter - ): Promise { + ): Promise { // Create API query. let apiQuery; try { - apiQuery = this.buildRelatedTopPostsApiQuery( + apiQuery = this.buildRelatedPostsApiQuery( period, metric, filter ); } catch ( contentHelperError ) { @@ -83,7 +83,7 @@ export class RelatedTopPostsProvider { // Fetch results from API and set the message. let data; try { - data = await this.fetchRelatedTopPostsFromWpEndpoint( apiQuery ); + data = await this.fetchRelatedPostsFromWpEndpoint( apiQuery ); } catch ( contentHelperError ) { return Promise.reject( contentHelperError ); } @@ -96,7 +96,7 @@ export class RelatedTopPostsProvider { } /** - * Generates the message that will be displayed above the related top posts. + * Generates the message that will be displayed above the related posts. * * @since 3.11.0 * @@ -113,7 +113,7 @@ export class RelatedTopPostsProvider { return sprintf( /* translators: 1: message such as "in category Foo" */ __( - 'No top posts %1$s were found for the specified period and metric.', + 'No related posts %1$s were found for the specified period and metric.', 'wp-parsely' ), apiQueryMessage ); @@ -121,22 +121,22 @@ export class RelatedTopPostsProvider { return sprintf( /* translators: 1: message such as "in category Foo", 2: period such as "last 7 days"*/ - __( 'Top posts %1$s in the %2$s.', 'wp-parsely' ), + __( 'Related posts %1$s in the %2$s.', 'wp-parsely' ), apiQueryMessage, getPeriodDescription( period, true ) ); } /** - * Fetches the related top posts data from the WordPress REST API. + * Fetches the related posts data from the WordPress REST API. * - * @param {RelatedTopPostsApiQuery} query + * @param {RelatedPostsApiQuery} query * @return {Promise>} Array of fetched posts. */ - private static async fetchRelatedTopPostsFromWpEndpoint( query: RelatedTopPostsApiQuery ): Promise { + private static async fetchRelatedPostsFromWpEndpoint( query: RelatedPostsApiQuery ): Promise { let response; try { - response = await apiFetch( { + response = await apiFetch( { path: addQueryArgs( '/wp-parsely/v1/stats/posts', { ...query.query, itm_source: 'wp-parsely-content-helper', @@ -160,17 +160,17 @@ export class RelatedTopPostsProvider { /** * Builds the query object used in the API for performing the related - * top posts request. + * posts request. * * @param {Period} period The period for which to fetch data. * @param {Metric} metric The metric to sort by. * @param {PostFilter} filter The selected filter type and value to use. * - * @return {RelatedTopPostsApiQuery} The query object. + * @return {RelatedPostsApiQuery} The query object. */ - private static buildRelatedTopPostsApiQuery( + private static buildRelatedPostsApiQuery( period: Period, metric:Metric, filter: PostFilter - ): RelatedTopPostsApiQuery { + ): RelatedPostsApiQuery { const commonQueryParams = { ...getApiPeriodParams( period ), limit: RELATED_POSTS_DEFAULT_LIMIT, diff --git a/src/content-helper/editor-sidebar/related-posts/related-posts.scss b/src/content-helper/editor-sidebar/related-posts/related-posts.scss new file mode 100644 index 000000000..f39d74e98 --- /dev/null +++ b/src/content-helper/editor-sidebar/related-posts/related-posts.scss @@ -0,0 +1,223 @@ +@import "../../common/css/variables"; +@import "../../common/css/functions"; + +.wp-parsely-related-posts { + display: flex; + flex-direction: column; + align-items: flex-start; + + .related-posts-description { + display: flex; + padding: to_rem(6px) 0 var(--grid-unit-20, to_rem(16px)) 0; + flex-direction: column; + align-items: flex-start; + gap: to_rem(10px); + align-self: stretch; + + /* Helper Text */ + font-size: to_rem(13px); + font-style: normal; + font-weight: 400; + line-height: to_rem(20px); /* 153.846% */ + } + + .related-posts-body { + display: flex; + flex-direction: column; + padding: to_rem(6px) 0 var(--grid-unit-20, to_rem(16px)) 0; + align-items: flex-start; + gap: to_rem(16px); + align-self: stretch; + + > div { + width: 100%; + } + + .related-posts-settings { + width: 100%; + display: flex; + flex-direction: column; + gap: to_rem(16px); + align-self: stretch; + + .components-base-control__field { + margin-bottom: 0; + } + + + } + + .related-posts-filter-settings { + display: flex; + flex-direction: column; + gap: var(--grid-unit-20, to_rem(16px)); + width: 100%; + + .related-posts-filter-types { + width: 100%; + + .components-toggle-group-control { + height: to_rem(40px); + + /** + * Workaround for the ToggleGroupControl animation: + * Prevents the animation from exiting the container. + */ + overflow: hidden; + + /** + * Workaround animation bug with ToggleGroupControl. + * This works by setting the button background to transparent and + * then setting the active item background to the sidebar black color. + * The background should only pop-up as visible after 0.5s, therefore + * hiding any janky animation. + */ + button { + background: transparent; + outline: transparent solid 2px; + outline-offset: -3px; + + &[data-active-item] { + background: var(--sidebar-black, #1e1e1e); + transition: + background 0s 0.5s, + border-radius 0s 0.5s, + box-shadow 0s 0.5s; + box-shadow: 0 -3px 0 0 #fff, 0 3px 0 0 #fff; + border-radius: 3px; + } + } + } + } + + .related-posts-filter-values { + width: 100%; + + .components-combobox-control__suggestions-container .components-flex { + height: to_rem(36px); + + input { + margin: 0 var(--grid-unit-15, to_rem(12px)); + } + } + } + } + + .related-posts-wrapper { + + .related-posts-descr { + font-size: to_rem(13px); + font-style: normal; + font-weight: 400; + line-height: to_rem(20px); /* 153.846% */ + } + + .related-posts-loading-message, + .related-posts-empty { + overflow: hidden; + color: var(--gray-900, #1e1e1e); + text-overflow: ellipsis; + font-size: to_rem(12px); + font-style: normal; + font-weight: 700; + line-height: var(--grid-unit-20, to_rem(16px)); /* 133.333% */ + } + } + + + .related-posts-list { + display: flex; + padding: to_rem(6px) 0 var(--grid-unit-20, to_rem(16px)) 0; + flex-direction: column; + align-items: flex-start; + gap: var(--grid-unit-20, to_rem(16px)); + align-self: stretch; + + .related-post-single { + display: flex; + flex-direction: column; + align-items: flex-start; + align-self: stretch; + border-radius: 2px; + border: 1px solid var(--Gutenberg-Gray-400, #ccc); + + .related-post-title { + display: flex; + padding: var(--grid-unit-20, to_rem(16px)); + flex-direction: column; + justify-content: center; + align-items: center; + gap: to_rem(8px); + align-self: stretch; + overflow: hidden; + text-overflow: ellipsis; + font-size: to_rem(14px); + font-style: normal; + font-weight: 600; + line-height: to_rem(20px); /* 142.857% */ + text-decoration-line: underline; + } + + .related-post-actions { + display: flex; + padding: 0 var(--grid-unit-10, to_rem(8px)); + align-items: center; + gap: var(--grid-unit-20, to_rem(16px)); + align-self: stretch; + flex-wrap: wrap; + border-top: 1px solid var(--gray-400, #ccc); + + .related-post-info { + display: flex; + flex-grow: 1; + gap: var(--grid-unit-10, to_rem(8px)); + align-items: center; + + > div:first-child { + flex: 1; + display: flex; + gap: var(--grid-unit-10, to_rem(8px)); + } + + > div:last-child { + display: flex; + gap: var(--grid-unit-10, to_rem(8px)); + } + + .related-post-metric { + display: flex; + align-items: center; + + .parsely-post-metric-data { + display: flex; + align-items: center; + gap: var(--grid-unit-5, to_rem(4px)); + } + } + + .related-post-linked { + display: flex; + align-items: center; + margin-left: auto; + fill: #008a20; + } + + .wp-parsely-icon { + + path { + fill: #1e1e1e; + } + + &:hover { + + path { + fill: #0073aa; + } + } + } + } + } + } + } + } +} diff --git a/src/content-helper/editor-sidebar/related-top-posts/component-filter-selection-controls.tsx b/src/content-helper/editor-sidebar/related-top-posts/component-filter-selection-controls.tsx deleted file mode 100644 index da8fb8f0c..000000000 --- a/src/content-helper/editor-sidebar/related-top-posts/component-filter-selection-controls.tsx +++ /dev/null @@ -1,205 +0,0 @@ -/** - * WordPress dependencies - */ -import { ComboboxControl, SelectControl } from '@wordpress/components'; -import { ComboboxControlOption } from '@wordpress/components/build-types/combobox-control/types'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { PostFilter, PostFilterType } from '../../common/utils/constants'; -import { SidebarPostData } from '../editor-sidebar'; - -/** - * Defines the props structure for FilterControls. - * - * @since 3.11.0 - */ -interface FilterControlsProps { - filter: PostFilter; - label?: string; - onFilterTypeChange: ( selection: string ) => void; - onFilterValueChange: ( selection: string | null | undefined ) => void; - postData: SidebarPostData; -} - -/** - * Defines the props structure for FilterTypeSelect. - * - * @since 3.11.0 - */ -interface FilterTypeSelectProps extends Omit {} - -/** - * Defines the props structure for FilterValueSelect. - * - * @since 3.11.0 - */ -interface FilterValueSelectProps extends Omit {} - -/** - * Returns the FilterSelectionControls component. - * - * @since 3.11.0 - * - * @param {FilterControlsProps} props The component's props. - * - * @return {JSX.Element} The FilterSelectionControls component. - */ -export const FilterSelectionControls = ( { - filter, label, onFilterTypeChange, onFilterValueChange, postData, -}: Readonly ): JSX.Element => { - /** - * Returns whether the filter type SelectControl should be displayed. - * - * @since 3.11.0 - * - * @return {boolean} Whether to display the filter type SelectControl. - */ - const shouldDisplayFilterTypes = (): boolean => { - if ( - ( postData.tags.length >= 1 && postData.categories.length >= 1 ) || - ( postData.tags.length >= 1 && postData.authors.length >= 1 ) || - ( postData.categories.length >= 1 && postData.authors.length >= 1 ) - ) { - return true; - } - - return false; - }; - - /** - * Returns whether the filter values ComboboxControl should be displayed. - * - * @since 3.11.0 - * - * @return {boolean} Whether to display the filter values ComboboxControl. - */ - const shouldDisplayFilterValues = (): boolean => { - if ( - ( PostFilterType.Tag === filter.type && postData.tags.length > 1 ) || - ( PostFilterType.Section === filter.type && postData.categories.length > 1 ) || - ( PostFilterType.Author === filter.type && postData.authors.length > 1 ) - ) { - return true; - } - - return false; - }; - - const displayLabelOnFilterValue = shouldDisplayFilterValues() && - ! shouldDisplayFilterTypes(); - - return ( - <> - { shouldDisplayFilterTypes() && - - } - { shouldDisplayFilterValues() && - - } - - ); -}; - -/** - * Returns a SelectControl containing the available filter types. - * - * Filter types for which there is no data will not be displayed. - * - * @since 3.11.0 - * - * @param {FilterTypeSelectProps} props The component's props. - * - * @return {JSX.Element} The resulting SelectControl. - */ -const FilterTypeSelectControl = ( { - filter, label, onFilterTypeChange, postData, -}: Readonly ): JSX.Element => { - return ( - onFilterTypeChange( selection ) } - value={ filter.type } - > - { postData.tags.length >= 1 && - - } - { postData.categories.length >= 1 && - - } - { postData.authors.length >= 1 && - - } - - ); -}; - -/** - * Returns a ComboBox control containing the available filter values for the - * selected filter type. - * - * @since 3.11.0 - * - * @param {FilterValueSelectProps} props The component's props. - * - * @return {JSX.Element} The resulting ComboboxControl. - */ -const FilterValueComboboxControl = ( { - filter, label, onFilterValueChange, postData, -}: Readonly ): JSX.Element => { - /** - * Returns the options that will populate the ComboboxControl. - * - * @since 3.11.0 - * - * @return {ComboboxControlOption[]} The resulting ComboboxControl options. - */ - const getOptions = (): ComboboxControlOption[] => { - if ( PostFilterType.Tag === filter.type ) { - return postData.tags.map( ( tag: string ) => ( { - value: tag, label: tag, - } ) ); - } - - if ( PostFilterType.Section === filter.type ) { - return postData.categories.map( ( section: string ) => ( { - value: section, label: section, - } ) ); - } - - if ( PostFilterType.Author === filter.type ) { - return postData.authors.map( ( author: string ) => ( { - value: author, label: author, - } ) ); - } - - return []; - }; - - return ( - onFilterValueChange( selection ) } - options={ getOptions() } - value={ filter.value } - /> - ); -}; diff --git a/src/content-helper/editor-sidebar/related-top-posts/component-list-item.tsx b/src/content-helper/editor-sidebar/related-top-posts/component-list-item.tsx deleted file mode 100644 index 595416778..000000000 --- a/src/content-helper/editor-sidebar/related-top-posts/component-list-item.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/** - * WordPress dependencies - */ -import { Dashicon } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { EditIcon } from '../../common/icons/edit-icon'; -import { OpenLinkIcon } from '../../common/icons/open-link-icon'; -import { ViewsIcon } from '../../common/icons/views-icon'; -import { getSmartShortDate } from '../../common/utils/date'; -import { - PostListItemMetric, - PostListItemProps, - getPostEditUrl, -} from '../../common/utils/post'; - -export function RelatedTopPostListItem( - { metric, post }: Readonly -): JSX.Element { - return ( -
  • - -

    - - { __( 'Date', 'wp-parsely' ) } - { getSmartShortDate( new Date( post.date ) ) } - - - { __( 'Author', 'wp-parsely' ) } - { post.author } - - } - avgEngagedIcon={ } - /> -

    -
  • - ); -} diff --git a/src/content-helper/editor-sidebar/related-top-posts/component-list.tsx b/src/content-helper/editor-sidebar/related-top-posts/component-list.tsx deleted file mode 100644 index 78cb320e9..000000000 --- a/src/content-helper/editor-sidebar/related-top-posts/component-list.tsx +++ /dev/null @@ -1,215 +0,0 @@ -/** - * WordPress dependencies - */ -import { Spinner } from '@wordpress/components'; -import { useEffect, useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { Telemetry } from '../../../js/telemetry/telemetry'; -import { ContentHelperError } from '../../common/content-helper-error'; -import { - Metric, - Period, - PostFilter, - PostFilterType, - isInEnum, -} from '../../common/utils/constants'; -import { PostData } from '../../common/utils/post'; -import { SidebarPostData } from '../editor-sidebar'; -import { - FilterSelectionControls, -} from './component-filter-selection-controls'; -import { RelatedTopPostListItem } from './component-list-item'; -import { RelatedTopPostsProvider } from './provider'; - -const FETCH_RETRIES = 1; - -/** - * Defines the props structure for RelatedTopPostList. - * - * @since 3.11.0 - */ -interface RelatedTopPostListProps { - initialFilter: PostFilter; - metric: Metric; - onFilterChange: ( type: PostFilterType, value: string ) => void; - period: Period; - postData: SidebarPostData; -} - -/** - * List of the related top posts. - * - * @param {RelatedTopPostListProps} props The component's props. - */ -export function RelatedTopPostList( { - initialFilter, metric, onFilterChange, period, postData, -} : Readonly ): JSX.Element { - const [ loading, setLoading ] = useState( true ); - const [ error, setError ] = useState(); - const [ message, setMessage ] = useState(); - const [ posts, setPosts ] = useState( [] ); - const [ filter, setFilter ] = useState( initialFilter ); - - /** - * Updates the filter type and sets its default value. - * - * @param {string} newFilterType The new filter type. - * - * @since 3.11.0 - */ - const updateFilterType = ( newFilterType: string ): void => { - if ( isInEnum( newFilterType, PostFilterType ) ) { - let value = ''; - const type = newFilterType as PostFilterType; - - if ( PostFilterType.Tag === type ) { - value = postData.tags[ 0 ]; - } - if ( PostFilterType.Section === type ) { - value = postData.categories[ 0 ]; - } - if ( PostFilterType.Author === type ) { - value = postData.authors[ 0 ]; - } - - if ( '' !== value ) { - onFilterChange( type, value ); - setFilter( { type, value } ); - Telemetry.trackEvent( 'related_top_posts_filter_type_changed', { filter_type: type } ); - } - } - }; - - /** - * Updates the filter value. - * - * @param {string} newFilterValue The new filter value. - * - * @since 3.11.0 - */ - const updateFilterValue = ( - newFilterValue: string | null | undefined - ): void => { - if ( typeof newFilterValue === 'string' ) { - onFilterChange( filter.type, newFilterValue ); - setFilter( { ...filter, value: newFilterValue } ); - } - }; - - useEffect( () => { - /** - * Returns the initial filter settings. - * - * The selection is based on whether the Post has tags or categories - * assigned to it. Otherwise, the filter is set to the first author. - * - * @since 3.11.0 - * - * @return {PostFilter} The initial filter settings. - */ - const getInitialFilterSettings = (): PostFilter => { - let value = ''; - let type = PostFilterType.Unavailable; - - if ( postData.tags.length >= 1 ) { - type = PostFilterType.Tag; - value = postData.tags[ 0 ]; - } else if ( postData.categories.length >= 1 ) { - type = PostFilterType.Section; - value = postData.categories[ 0 ]; - } else { - type = PostFilterType.Author; - value = postData.authors[ 0 ]; - } - - return { type, value }; - }; - - const fetchPosts = async ( retries: number ) => { - RelatedTopPostsProvider.getRelatedTopPosts( period, metric, filter ) - .then( ( result ): void => { - setPosts( result.posts ); - setMessage( result.message ); - setLoading( false ); - } ) - .catch( async ( err ) => { - if ( retries > 0 && err.retryFetch ) { - await new Promise( ( r ) => setTimeout( r, 500 ) ); - await fetchPosts( retries - 1 ); - } else { - setLoading( false ); - setError( err ); - } - } ); - }; - - const filterTypeIsTag = PostFilterType.Tag === filter.type; - const filterTypeIsUnavailable = PostFilterType.Unavailable === filter.type; - const noTagsExist = 0 === postData.tags.length; - const tagIsUnavailable = filterTypeIsTag && ! postData.tags.includes( filter.value ); - - setLoading( true ); - if ( filterTypeIsUnavailable || ( filterTypeIsTag && noTagsExist ) ) { - setFilter( getInitialFilterSettings() ); - } else if ( tagIsUnavailable ) { - setFilter( { type: PostFilterType.Tag, value: postData.tags[ 0 ] } ); - } else { - fetchPosts( FETCH_RETRIES ); - } - - return (): void => { - setLoading( false ); - setPosts( [] ); - setMessage( '' ); - setError( undefined ); - }; - }, [ period, metric, filter, postData ] ); - - const spinner: JSX.Element = ( -
    - -
    - ); - - const filterSelectionControls = ( - - ); - - // Show error message. - if ( error ) { - return ( - <> - { filterSelectionControls } - { error.Message( { className: 'parsely-top-posts-descr' } ) } - - ); - } - - return ( - <> - { filterSelectionControls } - { loading ? ( spinner ) : ( -
    -

    { message }

    -
      - { posts.map( ( post: PostData ): JSX.Element => - - ) } -
    -
    - ) } - - ); -} diff --git a/src/content-helper/editor-sidebar/smart-linking/component-block-overlay.tsx b/src/content-helper/editor-sidebar/smart-linking/component-block-overlay.tsx new file mode 100644 index 000000000..a4d4ed39f --- /dev/null +++ b/src/content-helper/editor-sidebar/smart-linking/component-block-overlay.tsx @@ -0,0 +1,197 @@ +/** + * WordPress dependencies + */ +import { Spinner } from '@wordpress/components'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { dispatch, useSelect } from '@wordpress/data'; +import { createPortal, useEffect, useState } from '@wordpress/element'; +import { addFilter } from '@wordpress/hooks'; +import { __ } from '@wordpress/i18n'; +import { registerPlugin } from '@wordpress/plugins'; + +/** + * Internal dependencies + */ +import { SmartLinkingStore } from './store'; + +/** + * Defines the props structure for BlockOverlay. + * + * @since 3.14.0 + */ +type BlockOverlayProps = { + selectedBlockClientId: string; + label: string; +}; + +/** + * Draws an overlay over the selected block. + * + * @since 3.14.0 + * + * @param {BlockOverlayProps} props The component's props. + * + * @return {JSX.Element} The JSX Element. + */ +export const BlockOverlay = ( { + selectedBlockClientId, + label, +}: Readonly ): JSX.Element => { + // Create a container element for the overlay. + const [ container ] = useState( document.createElement( 'div' ) ); + container.className = 'wp-parsely-block-overlay'; + if ( selectedBlockClientId === 'all' ) { + container.className += ' full-content-overlay'; + } + + // When clicking the overlay, we want the underlying block to be selected. + container.onclick = ( e ) => { + e.stopPropagation(); + e.stopImmediatePropagation(); + + if ( selectedBlockClientId === 'all' ) { + return; + } + + dispatch( 'core/block-editor' ).selectBlock( selectedBlockClientId, -1 ); + + // When nested blocks are selected, the block editor will focus the outermost block. + // We need to blur the focused element to avoid this. + const activeElement = container.ownerDocument.activeElement; + ( activeElement as HTMLElement ).blur(); + }; + + useEffect( () => { + if ( ! selectedBlockClientId ) { + return; + } + + /** + * If the selected block is the "All content" block, we need to append the overlay + * to the editor element instead of the block element. + */ + if ( selectedBlockClientId === 'all' ) { + const editorElement = document.querySelector( '.interface-navigable-region.interface-interface-skeleton__content' ); + editorElement?.appendChild( container ); + + // Set overflow to hidden. + editorElement?.setAttribute( 'style', 'overflow: hidden' ); + container.style.top = editorElement?.scrollTop + 'px'; + + return () => { + if ( editorElement?.contains( container ) ) { + editorElement.removeChild( container ); + } + // Restore overflow. + editorElement?.setAttribute( 'style', '' ); + container.style.top = ''; + }; + } + + const blockElement = document.querySelector( `[data-block="${ selectedBlockClientId }"]` ); + + // Disable changes on the block element. + blockElement?.setAttribute( 'contenteditable', 'false' ); + blockElement?.setAttribute( 'aria-disabled', 'true' ); + + // Insert the container in the block element. + blockElement?.appendChild( container ); + + // Remove the container on component unload. + return () => { + // Enable changes on the block element. + blockElement?.setAttribute( 'contenteditable', 'true' ); + blockElement?.removeAttribute( 'aria-disabled' ); + + if ( blockElement?.contains( container ) ) { + blockElement.removeChild( container ); + } + }; + } ); + + return createPortal( +
    + + { label } +
    , + container + ); +}; + +/** + * Draws an overlay over the full block editor, when the "All content" is selected. + * + * @since 3.14.0 + * + * @return {JSX.Element} The JSX Element. + */ +const BlockOverlayFullContent = ( ): JSX.Element => { + const { overlayBlocks } = useSelect( ( select ) => { + const { getOverlayBlocks } = select( SmartLinkingStore ); + + return { + overlayBlocks: getOverlayBlocks(), + }; + }, [] ); + + if ( overlayBlocks.includes( 'all' ) ) { + return ( + + ); + } + + return <>; +}; + +/** + * A higher-order component that adds a block overlay over a specific block, flagged by the Smart Linking store. + * + * @since 3.14.0 + */ +export const withBlockOverlay = createHigherOrderComponent( ( BlockEdit ) => { + return ( props ) => { + const { overlayBlocks } = useSelect( ( select ) => { + const { getOverlayBlocks } = select( SmartLinkingStore ); + + return { + overlayBlocks: getOverlayBlocks(), + }; + }, [] ); + + // If the block ID is currently on the overlayBlocks array, we should render the overlay. + if ( ! overlayBlocks.includes( props.clientId ) ) { + return ; + } + + return ( + <> + + + + ); + }; +}, 'withBlockOverlay' ); + +/** + * Initializes the block overlay, by adding the filter for individual blocks and + * registering a plugin for the full content overlay. + * + * @since 3.14.0 + */ +export const initBlockOverlay = (): void => { + addFilter( + 'editor.BlockEdit', + 'wpparsely/block-overlay', + withBlockOverlay + ); + + registerPlugin( 'wp-parsely-block-overlay', { + render: BlockOverlayFullContent, + } ); +}; diff --git a/src/content-helper/editor-sidebar/smart-linking/component-settings.tsx b/src/content-helper/editor-sidebar/smart-linking/component-settings.tsx new file mode 100644 index 000000000..eaa716cb7 --- /dev/null +++ b/src/content-helper/editor-sidebar/smart-linking/component-settings.tsx @@ -0,0 +1,200 @@ +/** + * WordPress dependencies + */ +import { + Disabled, + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, +} from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useEffect, useRef, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { InputRange } from '../../common/components/input-range'; +import { OnSettingChangeFunction } from '../editor-sidebar'; +import { DEFAULT_MAX_LINK_WORDS, DEFAULT_MAX_LINKS } from './smart-linking'; +import { SmartLinkingStore } from './store'; + +/** + * Defines the props structure for SmartLinkingSettings. + * + * @since 3.14.0 + */ +type SmartLinkingSettingsProps = { + disabled?: boolean; + selectedBlock?: string; + onSettingChange: OnSettingChangeFunction +}; + +/** + * Settings for the Smart Linking. + * + * @since 3.14.0 + * + * @param {SmartLinkingSettingsProps} props The component's props. + * + * @return {JSX.Element} The JSX Element. + */ +export const SmartLinkingSettings = ( { + disabled = false, + selectedBlock, + onSettingChange, +}: Readonly ): JSX.Element => { + /** + * Gets the value for the ToggleGroupControl. + * + * @since 3.14.0 + */ + const getToggleGroupValue = ( ) => { + if ( fullContent ) { + return 'all'; + } + + if ( selectedBlock && selectedBlock !== 'all' ) { + return 'selected'; + } + return 'all'; + }; + + const toggleGroupRef = useRef(); + const [ applyTo, setApplyTo ] = useState( getToggleGroupValue() ); + const [ wasProgrammaticallyClicked, setWasProgrammaticallyClicked ] = useState( false ); + + /** + * Gets the settings from the Smart Linking store. + * + * @since 3.14.0 + */ + const { + maxLinks, + maxLinkWords, + fullContent, + alreadyClicked, + } = useSelect( ( select ) => { + const { getMaxLinkWords, getMaxLinks, isFullContent, wasAlreadyClicked } = select( SmartLinkingStore ); + + return { + maxLinks: getMaxLinks(), + maxLinkWords: getMaxLinkWords(), + fullContent: isFullContent(), + alreadyClicked: wasAlreadyClicked(), + }; + }, [] ); + + const { + setMaxLinks, + setMaxLinkWords, + setFullContent, + setAlreadyClicked, + } = useDispatch( SmartLinkingStore ); + + /** + * Handles the change event of the ToggleGroupControl. + * It updates the settings based on the selected value. + * + * @since 3.14.0 + * + * @param {string|number|undefined} value The selected value. + */ + const onToggleGroupChange = ( value: string|number|undefined ) => { + // Ignore the onToggleGroupChange event if it was triggered programmatically. + if ( wasProgrammaticallyClicked ) { + setWasProgrammaticallyClicked( false ); + return; + } + + if ( disabled ) { + return; + } + + // Update the settings based on the selected value. + if ( value === 'all' ) { + setFullContent( true ); + } else { + setFullContent( false ); + } + setApplyTo( value as string ); + }; + + useEffect( () => { + if ( disabled ) { + return; + } + const value = getToggleGroupValue(); + setApplyTo( value ); + + // The first time selectedBlock changes, for some reason the ToggleGroupControl + // doesn't update the value. This workaround programmatically clicks the button + // to set the correct value. + if ( toggleGroupRef.current && value && ! alreadyClicked && selectedBlock ) { + const targetButton = toggleGroupRef.current.querySelector( `button[data-value="${ value }"]` ) as HTMLButtonElement; + if ( targetButton && targetButton.getAttribute( 'aria-checked' ) !== 'true' ) { + // Simulate a click on the button to set the correct value. + targetButton.click(); + // Flag that the button was clicked programmatically. + setWasProgrammaticallyClicked( true ); + // Flag that the button was already clicked as it's only needed on the first time. + setAlreadyClicked( true ); + } + } + }, [ selectedBlock, fullContent, disabled ] ); // eslint-disable-line + + return ( +
    +
    +
    + + + + + + +
    +
    + { + setMaxLinks( value ?? 1 ); + onSettingChange( 'SmartLinkingMaxLinks', value ?? DEFAULT_MAX_LINKS ); + } } + label={ __( 'Max Number of Links', 'wp-parsely' ) } + suffix={ __( 'Links', 'wp-parsely' ) } + min={ 1 } + max={ 20 } + initialPosition={ maxLinks } + disabled={ disabled } + /> + { + setMaxLinkWords( value ?? 1 ); + onSettingChange( 'SmartLinkingMaxLinkWords', value ?? DEFAULT_MAX_LINK_WORDS ); + } } + label={ __( 'Max Link Length', 'wp-parsely' ) } + suffix={ __( 'Words', 'wp-parsely' ) } + min={ 1 } + max={ 8 } + initialPosition={ maxLinkWords } + disabled={ disabled } + /> +
    +
    +
    + ); +}; diff --git a/src/content-helper/editor-sidebar/smart-linking/component.tsx b/src/content-helper/editor-sidebar/smart-linking/component.tsx new file mode 100644 index 000000000..9048fad9b --- /dev/null +++ b/src/content-helper/editor-sidebar/smart-linking/component.tsx @@ -0,0 +1,447 @@ +/** + * WordPress dependencies + */ +import { Button, Notice, PanelRow } from '@wordpress/components'; +import { dispatch, select, useDispatch, useSelect } from '@wordpress/data'; +import { useEffect, useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; +import { external, Icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { GutenbergFunction } from '../../../@types/gutenberg/types'; +import { Telemetry } from '../../../js/telemetry/telemetry'; +import { SidebarSettings, useSettings } from '../../common/settings'; +import { SmartLinkingSettings } from './component-settings'; +import { LinkSuggestion, SmartLinkingProvider } from './provider'; +import { SmartLinkingSettingsProps, SmartLinkingStore } from './store'; +import { escapeRegExp, replaceNthOccurrence } from './utils'; + +/** + * Defines the props structure for SmartLinkingPanel. + * + * @since 3.14.0 + */ +type SmartLinkingPanelProps = { + className?: string; + selectedBlockClientId?: string; + context?: SmartLinkingPanelContext; +} + +/** + * Defines the possible contexts in which the Smart Linking panel can be used. + * + * @since 3.14.0 + */ +export enum SmartLinkingPanelContext { + Unknown = 'unknown', + ContentHelperSidebar = 'content_helper_sidebar', + BlockInspector = 'block_inspector', +} + +/** + * Smart Linking Panel. + * + * @since 3.14.0 + * + * @param { Readonly } props The component's props. + * + * @return { JSX.Element } The JSX Element. + */ +export const SmartLinkingPanel = ( { + className, + selectedBlockClientId, + context = SmartLinkingPanelContext.Unknown, +}: Readonly ): JSX.Element => { + const { settings, setSettings } = useSettings(); + const [ hint, setHint ] = useState( null ); + + const { createNotice } = useDispatch( 'core/notices' ); + + /** + * Loads the Smart Linking store. + * + * @since 3.14.0 + */ + const { + loading, + fullContent, + overlayBlocks, + error, + suggestedLinks, + maxLinks, + maxLinkWords, + smartLinkingSettings, + } = useSelect( ( selectFn ) => { + const { + isLoading, + getOverlayBlocks, + getSuggestedLinks, + getError, + isFullContent, + getMaxLinks, + getMaxLinkWords, + getSmartLinkingSettings, + } = selectFn( SmartLinkingStore ); + return { + loading: isLoading(), + error: getError(), + maxLinks: getMaxLinks(), + maxLinkWords: getMaxLinkWords(), + fullContent: isFullContent(), + overlayBlocks: getOverlayBlocks(), + suggestedLinks: getSuggestedLinks(), + smartLinkingSettings: getSmartLinkingSettings(), + }; + }, [] ); + + /** + * Loads the Smart Linking store actions. + * + * @since 3.14.0 + */ + const { + setLoading, + setError, + setSuggestedLinks, + addOverlayBlock, + removeOverlayBlock, + setSmartLinkingSettings, + } = useDispatch( SmartLinkingStore ); + + /** + * Handles the change of a setting. + * + * Updates the settings in the Smart Linking store and the Settings Context. + * + * @since 3.14.0 + * + * @param { keyof SidebarSettings } setting The setting to change. + * @param { string | boolean | number } value The new value of the setting. + */ + const onSettingChange = ( setting: keyof SidebarSettings, value: string|boolean|number ): void => { + setSettings( { [ setting ]: value } ); + setSmartLinkingSettings( { [ setting ]: value } ); + }; + + /** + * Loads and prepares the Smart Linking settings from the Settings Context, + * if they are not already loaded. + * + * @since 3.14.0 + */ + useEffect( () => { + // If the smartLinkingSettings are not empty object, return early. + if ( Object.keys( smartLinkingSettings ).length > 0 ) { + return; + } + + // Load the settings from the WordPress database and store them in the Smart Linking store. + const newSmartLinkingSettings: SmartLinkingSettingsProps = { + maxLinksPerPost: settings.SmartLinkingMaxLinks, + maxLinkWords: settings.SmartLinkingMaxLinkWords, + }; + setSmartLinkingSettings( newSmartLinkingSettings ); + }, [ setSmartLinkingSettings, settings ] ); // eslint-disable-line react-hooks/exhaustive-deps + + /** + * Loads the selected block and post content. + * + * @since 3.14.0 + */ + const { + selectedBlock, + postContent, + } = useSelect( ( selectFn ) => { + const { getSelectedBlock, getBlock } = selectFn( 'core/block-editor' ) as GutenbergFunction; + const { getEditedPostContent } = selectFn( 'core/editor' ) as GutenbergFunction; + + return { + selectedBlock: selectedBlockClientId ? getBlock( selectedBlockClientId ) : getSelectedBlock(), + postContent: getEditedPostContent(), + }; + }, [ selectedBlockClientId ] ); + + /** + * Resets the hint when the selected block changes. + */ + useEffect( () => { + setHint( null ); + }, [ selectedBlock ] ); + + /** + * Generates smart links for the selected block or the entire post content. + * + * @since 3.14.0 + */ + const generateSmartLinks = () => async (): Promise => { + await setLoading( true ); + await setSuggestedLinks( null ); + await setError( null ); + + Telemetry.trackEvent( 'smart_linking_generate_pressed', { + is_full_content: fullContent, + selected_block: selectedBlock?.name ?? 'none', + context, + } ); + + // If selected block is not set, the overlay will be applied to the entire content. + await applyOverlay( fullContent ? 'all' : selectedBlock?.clientId ); + + // After 60 seconds without a response, timeout and remove any overlay. + const timeout = setTimeout( () => { + setLoading( false ); + Telemetry.trackEvent( 'smart_linking_generate_timeout', { + is_full_content: fullContent, + selected_block: selectedBlock?.name ?? 'none', + context, + } ); + + // If selected block is not set, the overlay will be removed from the entire content. + removeOverlay( fullContent ? 'all' : selectedBlock?.clientId ); + }, 60000 ); + + try { + const generatingFullContent = fullContent || ! selectedBlock; + let generatedLinks = []; + if ( selectedBlock?.originalContent && ! generatingFullContent ) { + generatedLinks = await SmartLinkingProvider.generateSmartLinks( + selectedBlock?.originalContent, + maxLinkWords, + maxLinks + ); + } else { + generatedLinks = await SmartLinkingProvider.generateSmartLinks( + postContent, + maxLinkWords, + maxLinks + ); + } + await setSuggestedLinks( generatedLinks ); + applySmartLinks( generatedLinks ); + } catch ( e: any ) { // eslint-disable-line @typescript-eslint/no-explicit-any + setError( e ); + createNotice( 'error', + __( 'There was a problem applying smart links.', 'wp-parsely' ), { + type: 'snackbar', + isDismissible: true, + } + ); + } finally { + await setLoading( false ); + await removeOverlay( fullContent ? 'all' : selectedBlock?.clientId ); + clearTimeout( timeout ); + } + }; + + /** + * Applies the smart links to the selected block or the entire post content. + * + * @since 3.14.0 + * + * @param {LinkSuggestion[]} links The smart links to apply. + */ + const applySmartLinks = ( links: LinkSuggestion[] ): void => { + Telemetry.trackEvent( 'smart_linking_applied', { + is_full_content: fullContent, + selected_block: selectedBlock?.name ?? 'none', + links_count: links.length, + context, + } ); + + // Get the original content of the selected block or the entire post content. + let originalContent = ''; + if ( selectedBlock && ! fullContent ) { + originalContent = selectedBlock.attributes.content; + } else { + originalContent = postContent; + } + + let newContent = originalContent; // Fallback to original content if no links are found. + for ( const link of links ) { + // Check if the content already contains the link, skip if so. + if ( originalContent.includes( link.title ) && originalContent.includes( link.href ) ) { + continue; + } + + // Escape the link text to convert regex special characters to literal characters. + link.text = escapeRegExp( link.text ); + + // Check if the amount of link.text occurrences in the newContent is bigger than the amount of + // link.text occurrences in the originalContent, if so, it means that we've introduced another + // occurrence of the link.text in the newContent, so we need to increase the offset. + const linkTextRegex = new RegExp( link.text, 'g' ); + const newContentMatches = newContent.match( linkTextRegex ); + const originalContentMatches = originalContent.match( linkTextRegex ); + if ( ( newContentMatches && originalContentMatches ) && + newContentMatches?.length > originalContentMatches?.length ) { + link.offset++; + } + + const anchor = `${ link.text }`; + + // Regex that searches for the link.text, but if the text is inside an HTML anchor, + // the anchor itself is also selected and replaced with the new anchor. + const searchRegex = new RegExp( `(${ link.text }|]*>${ link.text })` ); + newContent = replaceNthOccurrence( newContent, searchRegex, anchor, link.offset ); + } + + // Either update the selected block or the entire post content. + if ( selectedBlock && ! fullContent ) { + dispatch( 'core/block-editor' ).updateBlockAttributes( selectedBlock.clientId, { content: newContent } ); + } else { + dispatch( 'core/editor' ).editPost( { content: newContent } ); + } + + // Snack bar notification. + createNotice( 'success', + /* translators: 1 - number of smart links generated */ + sprintf( __( '%s links applied.', 'wp-parsely' ), links.length ), + { + type: 'snackbar', + isDismissible: true, + } + ); + }; + + /** + * Applies the overlay to the selected block or the entire post content. + * + * @since 3.14.0 + * + * @param {string} clientId The client ID of the block to apply the overlay to.\ + * If set to 'all', the overlay will be applied to the entire post content. + */ + const applyOverlay = async ( clientId: string = 'all' ): Promise => { + await addOverlayBlock( clientId ); + disableSave(); + }; + + /** + * Removes the overlay from the selected block or the entire post content. + * + * @since 3.14.0 + * + * @param {string} clientId The client ID of the block to remove the overlay from. + * If set to 'all', the overlay will be removed from the entire post content. + */ + const removeOverlay = async ( clientId: string = 'all' ): Promise => { + await removeOverlayBlock( clientId ); + + // Select a block after removing the overlay, only if we're using the block inspector. + if ( context === SmartLinkingPanelContext.BlockInspector ) { + if ( 'all' !== clientId && ! fullContent ) { + dispatch( 'core/block-editor' ).selectBlock( clientId ); + } else { + const firstBlock = select( 'core/block-editor' ).getBlockOrder()[ 0 ]; + // Select the first block in the post. + dispatch( 'core/block-editor' ).selectBlock( firstBlock ); + } + } + + // If there are no more overlay blocks, enable save. + if ( overlayBlocks.length === 0 ) { + enableSave(); + } + }; + + /** + * Disables the save button and locks post auto-saving. + * + * @since 3.14.0 + */ + const disableSave = (): void => { + // Lock post saving. + dispatch( 'core/editor' ).lockPostSaving( 'wp-parsely-block-overlay' ); + + // Disable save buttons. + const saveButtons = document.querySelectorAll( '.edit-post-header__settings>[type="button"]' ); + saveButtons.forEach( ( button ) => { + button.setAttribute( 'disabled', 'disabled' ); + } ); + }; + + /** + * Enables the save button and unlocks post auto-saving. + * + * @since 3.14.0 + */ + const enableSave = (): void => { + // Enable save buttons. + const saveButtons = document.querySelectorAll( '.edit-post-header__settings>[type="button"]' ); + saveButtons.forEach( ( button ) => { + button.removeAttribute( 'disabled' ); + } ); + + // Unlock post saving. + dispatch( 'core/editor' ).unlockPostSaving( 'wp-parsely-block-overlay' ); + }; + + return ( +
    + +
    + { __( + 'Automatically insert links to your most relevant, top performing content.', + 'wp-parsely' + ) } + +
    + { error && ( + + { error.message } + + ) } + { suggestedLinks !== null && ( + + { + /* translators: 1 - number of smart links generated */ + sprintf( __( 'Successfully added %s smart links.', 'wp-parsely' ), suggestedLinks.length ) + } + + ) } + +
    + + { hint && + setHint( null ) } + className="wp-parsely-smart-linking-hint" + > + { __( 'Hint:', 'wp-parsely' ) } { hint } + + } +
    +
    +
    + ); +}; diff --git a/src/content-helper/editor-sidebar/smart-linking/provider.ts b/src/content-helper/editor-sidebar/smart-linking/provider.ts new file mode 100644 index 000000000..3f53e9be4 --- /dev/null +++ b/src/content-helper/editor-sidebar/smart-linking/provider.ts @@ -0,0 +1,86 @@ +/** + * WordPress dependencies + */ +import apiFetch from '@wordpress/api-fetch'; +import { addQueryArgs } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import { ContentHelperError, ContentHelperErrorCode } from '../../common/content-helper-error'; +import { DEFAULT_MAX_LINK_WORDS, DEFAULT_MAX_LINKS } from './smart-linking'; + +/** + * Structure of a link suggestion returned by the + * `content-suggestions/suggest-linked-reference` endpoint. + * + * @since 3.14.0 + */ +export type LinkSuggestion = { + href: string; + text: string; + title: string; + offset: number; +}; + +/** + * Specifies the form of the response returned by the + * `content-suggestions/suggest-linked-reference` WordPress REST API endpoint. + * + * @since 3.14.0 + */ +interface SmartLinkingApiResponse { + error?: Error; + data: LinkSuggestion[]; +} + +/** + * Returns data from the `content-suggestions/suggest-linked-reference` WordPress REST API + * endpoint. + * + * @since 3.14.0 + */ +export class SmartLinkingProvider { + /** + * Returns a list of suggested links for the given content. + * + * @param {string} content The content to generate links for. + * @param {number} maxLinkWords The maximum number of words in links. + * @param {number} maxLinksPerPost The maximum number of links to return. + * + * @return {Promise} The resulting list of links. + */ + static async generateSmartLinks( + content: string, + maxLinkWords: number = DEFAULT_MAX_LINK_WORDS, + maxLinksPerPost: number = DEFAULT_MAX_LINKS, + ): Promise { + let response; + + try { + response = await apiFetch( { + method: 'POST', + path: addQueryArgs( '/wp-parsely/v1/content-suggestions/suggest-linked-reference', { + max_link_words: maxLinkWords, + max_links: maxLinksPerPost, + } ), + data: { + content, + }, + } ); + } catch ( wpError: any ) { // eslint-disable-line @typescript-eslint/no-explicit-any + return Promise.reject( new ContentHelperError( wpError.message, wpError.code ) ); + } + + if ( response?.error ) { + return Promise.reject( + new ContentHelperError( + response.error.message, + ContentHelperErrorCode.ParselyApiResponseContainsError, + ), + ); + } + + return response.data ?? []; + } +} diff --git a/src/content-helper/editor-sidebar/smart-linking/smart-linking.scss b/src/content-helper/editor-sidebar/smart-linking/smart-linking.scss new file mode 100644 index 000000000..f716222fa --- /dev/null +++ b/src/content-helper/editor-sidebar/smart-linking/smart-linking.scss @@ -0,0 +1,159 @@ +@import "../../common/css/variables"; +@import "../../common/css/functions"; + +.wp-parsely-block-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.85); + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + + .wp-parsely-block-overlay-label { + flex-grow: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + pointer-events: none; + user-select: none; + } + + svg { + width: to_rem(25px); + height: to_rem(25px); + } + + &.full-content-overlay { + z-index: 999; + font-size: to_rem(20px); + + span { + margin-top: to_rem(15px); + } + + svg { + width: to_rem(50px); + height: to_rem(50px); + } + } +} + +.wp-parsely-panel { + + .wp-parsely-icon { + order: -1; + margin-right: to_rem(5px); + } +} + +.wp-parsely-smart-linking { + + /* Override the default block inspector styles for the panel. */ + .components-panel__row { + flex-direction: column; + margin-bottom: 0; + } + + .components-base-control { + margin-bottom: 0; + + &:last-child { + margin-bottom: 0; + } + + .components-panel__row { + margin-bottom: 0; + } + } + + .smart-linking-text { + display: flex; + padding: to_rem(6px) 0 var(--grid-unit-20, to_rem(16px)) 0; + flex-direction: column; + align-items: flex-start; + gap: to_rem(10px); + align-self: stretch; + } + + .parsely-panel-settings-body { + + .smart-linking-block-select { + display: flex; + padding: to_rem(6px) 0 var(--grid-unit-20, to_rem(16px)) 0; + flex-direction: column; + gap: to_rem(24px); + align-self: stretch; + + .components-toggle-group-control { + height: 40px; + + /** + * Workaround for the ToggleGroupControl animation: + * Prevents the animation from exiting the container. + */ + overflow: hidden; + + /** + * Workaround animation bug with ToggleGroupControl. + * This works by setting the button background to transparent and + * then setting the active item background to the sidebar black color. + * The background should only pop-up as visible after 0.5s, therefore + * hiding any janky animation. + */ + button { + background: transparent; + outline: transparent solid 2px; + outline-offset: -3px; + + &[data-active-item] { + background: var(--sidebar-black, #1e1e1e); + transition: + background 0s 0.5s, + border-radius 0s 0.5s, + box-shadow 0s 0.5s; + box-shadow: 0 -3px 0 0 #fff, 0 3px 0 0 #fff; + border-radius: 3px; + } + } + } + } + + .smart-linking-settings { + display: flex; + flex-direction: column; + gap: var(--grid-unit-20, to_rem(16px)); + align-self: stretch; + } + + } + + + .smart-linking-generate { + display: flex; + padding: to_rem(6px) 0 var(--grid-unit-20, to_rem(16px)) 0; + flex-direction: column; + align-self: stretch; + + /* stylelint-disable-next-line no-descending-specificity */ + button { + display: flex; + align-items: center; + align-self: stretch; + width: 100%; + justify-content: center; + } + } + + .wp-parsely-smart-linking-suggested-links { + margin: 0; + } + + .wp-parsely-smart-linking-hint { + margin: 0; + } +} diff --git a/src/content-helper/editor-sidebar/smart-linking/smart-linking.tsx b/src/content-helper/editor-sidebar/smart-linking/smart-linking.tsx new file mode 100644 index 000000000..eda94583a --- /dev/null +++ b/src/content-helper/editor-sidebar/smart-linking/smart-linking.tsx @@ -0,0 +1,120 @@ +/** + * WordPress dependencies + */ +import { InspectorControls } from '@wordpress/block-editor'; +import { PanelBody } from '@wordpress/components'; +import { compose, createHigherOrderComponent } from '@wordpress/compose'; +import { addFilter } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import { Telemetry } from '../../../js/telemetry/telemetry'; +import { BetaBadge } from '../../common/components/beta-badge'; +import { LeafIcon } from '../../common/icons/leaf-icon'; +import { SettingsProvider, SidebarSettings, useSettings } from '../../common/settings'; +import { VerifyCredentials } from '../../common/verify-credentials'; +import { getSettingsFromJson } from '../editor-sidebar'; +import { SmartLinkingPanel, SmartLinkingPanelContext } from './component'; +import { initBlockOverlay } from './component-block-overlay'; +import './smart-linking.scss'; + +export const DEFAULT_MAX_LINKS = 10; + +export const DEFAULT_MAX_LINK_WORDS = 4; + +/** + * Higher order component to add the settings provider to the block edit component. + * This is required to provide the settings to the smart linking panel. + * + * @since 3.14.0 + */ +const withSettingsProvider = createHigherOrderComponent( ( BlockEdit ) => { + return ( props ) => { + if ( ! props.isSelected || props.name !== 'core/paragraph' ) { + return ; + } + + return ( + + + + ); + }; +}, 'withSettingsProvider' ); + +/** + * Smart linking inspector control panel component. + * + * @since 3.14.0 + */ +const SmartLinkingInspectorControlPanel = createHigherOrderComponent( ( BlockEdit ) => { + return ( props ) => { + if ( ! props.isSelected || props.name !== 'core/paragraph' ) { + return ; + } + + const { settings, setSettings } = useSettings(); + return ( + <> + + { /* @ts-ignore */ } + + } + onToggle={ ( next ) => { + setSettings( { SmartLinkingOpen: next } ); + Telemetry.trackEvent( 'smart_linking_block_inspector_panel_toggled', { open: next } ); + } } + > + + + + + + + ); + }; +}, 'withSmartLinkingPanel' ); + +/** + * The smart linking panel with settings provider. + * This is the final component that is added to the block inspector. + * + * @since 3.14.0 + */ +const SmartLinkingPanelWithSettingsProvider = compose( + withSettingsProvider, + SmartLinkingInspectorControlPanel +); + +/** + * Initializes the smart linking, by adding the smart linking panel to the paragraph block. + * Also registers the block overlay container. + * + * @since 3.14.0 + */ +export const initSmartLinking = (): void => { + /** + * Add smart linking inspector control panel to paragraph block. + */ + addFilter( + 'editor.BlockEdit', + 'wpparsely/smart-linking-inspector-control-panel', + SmartLinkingPanelWithSettingsProvider + ); + + /** + * Initialize the block overlay component. + */ + initBlockOverlay(); +}; diff --git a/src/content-helper/editor-sidebar/smart-linking/store.ts b/src/content-helper/editor-sidebar/smart-linking/store.ts new file mode 100644 index 000000000..d531f514f --- /dev/null +++ b/src/content-helper/editor-sidebar/smart-linking/store.ts @@ -0,0 +1,269 @@ +/** + * WordPress dependencies + */ +import { createReduxStore, register } from '@wordpress/data'; +import { ContentHelperError } from '../../common/content-helper-error'; +import { DEFAULT_MAX_LINKS, DEFAULT_MAX_LINK_WORDS } from './smart-linking'; + +/** + * Internal dependencies + */ +import { LinkSuggestion } from './provider'; + +/** + * Defines the props structure for SmartLinkingSettings. + * + * @since 3.14.0 + */ +export type SmartLinkingSettingsProps = { + maxLinkWords?: number; + maxLinksPerPost?: number; +}; + +/** + * The shape of the SmartLinking store state. + * + * @since 3.14.0 + */ +type SmartLinkingState = { + isLoading: boolean; + fullContent: boolean; + error: ContentHelperError | null; + settings: SmartLinkingSettingsProps; + suggestedLinks: LinkSuggestion[] | null; + overlayBlocks: string[]; + wasAlreadyClicked: boolean; +}; + +/** Actions */ +interface SetLoadingAction { + type: 'SET_LOADING'; + isLoading: boolean; +} + +interface SetErrorAction { + type: 'SET_ERROR'; + error: ContentHelperError | null; +} + +interface SetOverlayBlocksAction { + type: 'SET_OVERLAY_BLOCKS'; + overlayBlocks: string[]; +} + +interface AddOverlayBlockAction { + type: 'ADD_OVERLAY_BLOCK'; + block: string; +} + +interface RemoveOverlayBlockAction { + type: 'REMOVE_OVERLAY_BLOCK'; + block: string; +} + +interface SetFullContentAction { + type: 'SET_FULL_CONTENT'; + fullContent: boolean; +} + +interface SetSettingsAction { + type: 'SET_SETTINGS'; + settings: SmartLinkingSettingsProps; +} + +interface SetSuggestedLinksAction { + type: 'SET_SUGGESTED_LINKS'; + suggestedLinks: LinkSuggestion[] | null; +} + +interface SetWasAlreadyClickedAction { + type: 'SET_WAS_ALREADY_CLICKED'; + wasAlreadyClicked: boolean; +} + +type ActionTypes = SetLoadingAction | SetOverlayBlocksAction | SetSettingsAction | + AddOverlayBlockAction | RemoveOverlayBlockAction |SetFullContentAction | + SetSuggestedLinksAction | SetErrorAction| SetWasAlreadyClickedAction; + +const defaultState: SmartLinkingState = { + isLoading: false, + fullContent: false, + suggestedLinks: null, + error: null, + settings: { }, + overlayBlocks: [], + wasAlreadyClicked: false, +}; + +/** + * The SmartLinking store. + * + * @since 3.14.0 + */ +export const SmartLinkingStore = createReduxStore( 'wp-parsely/smart-linking', { + initialState: defaultState, + reducer( state: SmartLinkingState = defaultState, action: ActionTypes ): SmartLinkingState { + switch ( action.type ) { + case 'SET_LOADING': + return { + ...state, + isLoading: action.isLoading, + }; + case 'SET_OVERLAY_BLOCKS': + return { + ...state, + overlayBlocks: action.overlayBlocks, + }; + case 'SET_ERROR': + return { + ...state, + error: action.error, + }; + case 'ADD_OVERLAY_BLOCK': + return { + ...state, + overlayBlocks: [ ...state.overlayBlocks, action.block ], + }; + case 'REMOVE_OVERLAY_BLOCK': + // If the action is 'all', remove all overlay blocks. + if ( action.block === 'all' ) { + return { + ...state, + overlayBlocks: [], + }; + } + return { + ...state, + overlayBlocks: state.overlayBlocks.filter( ( block ) => block !== action.block ), + }; + case 'SET_FULL_CONTENT': + return { + ...state, + fullContent: action.fullContent, + }; + case 'SET_SETTINGS': + return { + ...state, + settings: { + ...state.settings, + ...action.settings, + }, + }; + case 'SET_SUGGESTED_LINKS': + return { + ...state, + suggestedLinks: action.suggestedLinks, + }; + case 'SET_WAS_ALREADY_CLICKED': + return { + ...state, + wasAlreadyClicked: action.wasAlreadyClicked, + }; + default: + return state; + } + }, + actions: { + setLoading( isLoading: boolean ): SetLoadingAction { + return { + type: 'SET_LOADING', + isLoading, + }; + }, + setOverlayBlocks( overlayBlocks: string[] ): SetOverlayBlocksAction { + return { + type: 'SET_OVERLAY_BLOCKS', + overlayBlocks, + }; + }, + setError( error: ContentHelperError | null ): SetErrorAction { + return { + type: 'SET_ERROR', + error, + }; + }, + addOverlayBlock( block: string ): AddOverlayBlockAction { + return { + type: 'ADD_OVERLAY_BLOCK', + block, + }; + }, + removeOverlayBlock( block: string ): RemoveOverlayBlockAction { + return { + type: 'REMOVE_OVERLAY_BLOCK', + block, + }; + }, + setFullContent( fullContent: boolean ): SetFullContentAction { + return { + type: 'SET_FULL_CONTENT', + fullContent, + }; + }, + setSmartLinkingSettings( settings: SmartLinkingSettingsProps ): SetSettingsAction { + return { + type: 'SET_SETTINGS', + settings, + }; + }, + setMaxLinkWords( maxLinkWords: number ): SetSettingsAction { + return { + type: 'SET_SETTINGS', + settings: { + maxLinkWords, + }, + }; + }, + setMaxLinks( maxLinksPerPost: number ): SetSettingsAction { + return { + type: 'SET_SETTINGS', + settings: { + maxLinksPerPost, + }, + }; + }, + setSuggestedLinks( suggestedLinks: LinkSuggestion[] | null ): SetSuggestedLinksAction { + return { + type: 'SET_SUGGESTED_LINKS', + suggestedLinks, + }; + }, + setAlreadyClicked( wasAlreadyClicked: boolean ): SetWasAlreadyClickedAction { + return { + type: 'SET_WAS_ALREADY_CLICKED', + wasAlreadyClicked, + }; + }, + }, + selectors: { + isLoading( state: SmartLinkingState ): boolean { + return state.isLoading; + }, + isFullContent( state: SmartLinkingState ): boolean { + return state.fullContent; + }, + getError( state: SmartLinkingState ): ContentHelperError | null { + return state.error; + }, + getSmartLinkingSettings( state: SmartLinkingState ): SmartLinkingSettingsProps { + return state.settings; + }, + getOverlayBlocks( state: SmartLinkingState ): string[] { + return state.overlayBlocks; + }, + getMaxLinkWords( state: SmartLinkingState ): number { + return state.settings.maxLinkWords ?? DEFAULT_MAX_LINK_WORDS; + }, + getMaxLinks( state: SmartLinkingState ): number { + return state.settings.maxLinksPerPost ?? DEFAULT_MAX_LINKS; + }, + getSuggestedLinks( state: SmartLinkingState ): LinkSuggestion[] | null { + return state.suggestedLinks; + }, + wasAlreadyClicked( state: SmartLinkingState ): boolean { + return state.wasAlreadyClicked; + }, + }, +} ); + +register( SmartLinkingStore ); diff --git a/src/content-helper/editor-sidebar/smart-linking/utils.ts b/src/content-helper/editor-sidebar/smart-linking/utils.ts new file mode 100644 index 000000000..681098612 --- /dev/null +++ b/src/content-helper/editor-sidebar/smart-linking/utils.ts @@ -0,0 +1,51 @@ +/** + * Replaces the nth occurrence of a substring within a string. + * + * If the search string does not occur n times, the original string is returned. + * + * @since 3.14.0 + * + * @param {string} inputString The original string. + * @param {string} search The substring to search for. + * @param {string} replacement The replacement string. + * @param {number} n The 0-based index of the occurrence to replace. + * + * @return {string} The string containing the replacement, or the original string. + */ +export function replaceNthOccurrence( + inputString: string, search: RegExp, replacement: string, n: number +): string { + let match; + let i = 0; + + // Ensure the global flag is set to find all occurrences. + const globalSearch = new RegExp( + search.source, 'g' + ( search.ignoreCase ? 'i' : '' ) + ( search.multiline ? 'm' : '' ) + ); + + while ( ( match = globalSearch.exec( inputString ) ) !== null ) { + if ( i === n ) { + // Replace the nth occurrence + const contentBefore = inputString.substring( 0, match.index ); + const contentAfter = inputString.substring( globalSearch.lastIndex ); + return contentBefore + replacement + contentAfter; + } + i++; + } + + // Return the original string if the nth occurrence is not found. + return inputString; +} + +/** + * Escapes special characters in a string for use in a regular expression. + * + * @since 3.14.0 + * + * @param {string} string - The string to be escaped. + * + * @return {string} The escaped string. + */ +export function escapeRegExp( string: string ): string { + return string.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); // $& means the whole matched string. +} diff --git a/src/content-helper/editor-sidebar/tabs/sidebar-performance-tab.tsx b/src/content-helper/editor-sidebar/tabs/sidebar-performance-tab.tsx new file mode 100644 index 000000000..4f60e388c --- /dev/null +++ b/src/content-helper/editor-sidebar/tabs/sidebar-performance-tab.tsx @@ -0,0 +1,40 @@ +/** + * Internal dependencies + */ +import { + Panel, +} from '@wordpress/components'; +import { Period } from '../../common/utils/constants'; +import { VerifyCredentials } from '../../common/verify-credentials'; +import { PerformanceStats } from '../performance-details/component'; + +/** + * SidebarPerformanceTab component props. + * + * @since 3.14.0 + */ +type SidebarPerformanceTabProps = { + period: Period; +} + +/** + * SidebarPerformanceTab component. + * Renders the Performance tab in the Content Helper Sidebar. + * + * @since 3.14.0 + * + * @param { SidebarPerformanceTabProps } props The component's props. + * + * @return { JSX.Element } The SidebarPerformanceTab JSX Element. + */ +export const SidebarPerformanceTab = ( + { period }: Readonly +): JSX.Element => { + return ( + + + + + + ); +}; diff --git a/src/content-helper/editor-sidebar/tabs/sidebar-tools-tab.tsx b/src/content-helper/editor-sidebar/tabs/sidebar-tools-tab.tsx new file mode 100644 index 000000000..325be6e66 --- /dev/null +++ b/src/content-helper/editor-sidebar/tabs/sidebar-tools-tab.tsx @@ -0,0 +1,93 @@ +/** + * WordPress dependencies + */ +import { Panel, PanelBody } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { SidebarSettings, useSettings } from '../../common/settings'; +import { VerifyCredentials } from '../../common/verify-credentials'; +import { RelatedPostsPanel } from '../related-posts/component'; +import { SmartLinkingPanel, SmartLinkingPanelContext } from '../smart-linking/component'; +import { TitleSuggestionsPanel } from '../title-suggestions/component'; + +/** + * SidebarToolsTab component props. + * + * @since 3.14.0 + */ +type SidebarToolsTabProps = { + trackToggle: ( panel: string, next: boolean ) => void +} + +/** + * SidebarToolsTab component. + * Renders the Tools tab in the Content Helper sidebar. + * + * @since 3.14.0 + * + * @param { SidebarToolsTabProps } props The component's props. + */ +export const SidebarToolsTab = ( + { trackToggle }: Readonly +): JSX.Element => { + const { settings, setSettings } = useSettings(); + + return ( + + { + setSettings( { + TitleSuggestionsSettings: { + ...settings.TitleSuggestionsSettings, + Open: next, + }, + } ); + trackToggle( 'title_suggestions', next ); + } } + > + + + + + + { + setSettings( { + SmartLinkingOpen: next, + } ); + trackToggle( 'smart_linking', next ); + } } + > + + + + + + { + setSettings( { + RelatedPostsOpen: next, + } ); + trackToggle( 'related_top_posts', next ); + } } + > + { + + + + } + + + ); +}; diff --git a/src/content-helper/editor-sidebar/title-suggestions/component-pinned.tsx b/src/content-helper/editor-sidebar/title-suggestions/component-pinned.tsx new file mode 100644 index 000000000..2eacb4b75 --- /dev/null +++ b/src/content-helper/editor-sidebar/title-suggestions/component-pinned.tsx @@ -0,0 +1,72 @@ +/** + * WordPress dependencies + */ +import { Panel, PanelBody } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { pinSmall } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { Telemetry } from '../../../js/telemetry/telemetry'; +import { TitleSuggestion } from './component-title-suggestion'; +import { Title, TitleType } from './store'; + +/** + * Props for the Pinned Title Suggestions component. + * + * @since 3.14.0 + */ +type PinnedTitleSuggestionsProps = { + pinnedTitles: Title[]; + isOpen: boolean; +}; + +/** + * Renders the Pinned Title Suggestions panel. + * + * @since 3.14.0 + * + * @param {PinnedTitleSuggestionsProps} props The component's props. + */ +export const PinnedTitleSuggestions = ( { + pinnedTitles, + isOpen, +}: Readonly ): JSX.Element => { + const [ isCollapsed, setIsCollapsed ] = useState( isOpen ); + + /** + * Toggles the collapse state of the panel. + * + * @since 3.14.0 + */ + const toggleCollapse = () => { + setIsCollapsed( ! isCollapsed ); + Telemetry.trackEvent( 'title_suggestions_pinned_toggle', { + isOpen: ! isCollapsed, + pinnedTitles: pinnedTitles.length, + } ); + }; + + return ( + + +
    + { pinnedTitles.map( ( title ) => ( + + ) ) } +
    +
    +
    + ); +}; diff --git a/src/content-helper/editor-sidebar/title-suggestions/component-settings.tsx b/src/content-helper/editor-sidebar/title-suggestions/component-settings.tsx index 7698edc1c..eca34ba34 100644 --- a/src/content-helper/editor-sidebar/title-suggestions/component-settings.tsx +++ b/src/content-helper/editor-sidebar/title-suggestions/component-settings.tsx @@ -1,30 +1,24 @@ -/** - * WordPress dependencies - */ -import { BaseControl, Button } from '@wordpress/components'; -import { useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import { settings } from '@wordpress/icons'; - /** * Internal dependencies */ import { Telemetry } from '../../../js/telemetry/telemetry'; -import { PersonaProp, PersonaSelector, getPersonaLabel } from '../../common/components/persona-selector'; -import { ToneProp, ToneSelector, getToneLabel } from '../../common/components/tone-selector'; -import { LeafIcon } from '../../common/icons/leaf-icon'; -import { SidebarSettings } from '../editor-sidebar'; +import { getPersonaLabel, PersonaProp, PersonaSelector } from '../../common/components/persona-selector'; +import { getToneLabel, ToneProp, ToneSelector } from '../../common/components/tone-selector'; +import { SidebarSettings } from '../../common/settings'; /** * Props for the Title Suggestions Settings component. * * @since 3.13.0 + * @since 3.14.0 Removed isOpen prop. */ type TitleSuggestionsSettingsProps = { isLoading?: boolean, - isOpen: boolean, onPersonaChange: ( persona: PersonaProp | string ) => void, - onSettingChange: ( key: keyof SidebarSettings, value: string|boolean ) => void, + onSettingChange: ( + key: keyof SidebarSettings[ 'TitleSuggestionsSettings'], + value: string|boolean + ) => void, onToneChange: ( tone: ToneProp | string ) => void, persona: PersonaProp, tone: ToneProp, @@ -34,77 +28,47 @@ type TitleSuggestionsSettingsProps = { * Component that renders the settings for the Title Suggestions. * * @since 3.13.0 + * @since 3.14.0 Removed isOpen prop as the component is no longer collapsible. * * @param {TitleSuggestionsSettingsProps} props The component props. */ export const TitleSuggestionsSettings = ( { isLoading, - isOpen, onPersonaChange, - onSettingChange, onToneChange, persona, tone, }: Readonly ): JSX.Element => { - const [ isSettingActive, setIsSettingActive ] = useState( isOpen ); - - const toggleSetting = () => { - onSettingChange( 'TitleSuggestionsSettingsOpen', ! isSettingActive ); - setIsSettingActive( ! isSettingActive ); - Telemetry.trackEvent( 'title_suggestions_ai_settings_toggled', { - is_active: ! isSettingActive, - } ); - }; - return ( -
    -
    - - -
    - { isSettingActive && ( -
    - { - onToneChange( selectedTone ); - } } - onDropdownChange={ ( selectedTone ) => { - Telemetry.trackEvent( 'title_suggestions_ai_tone_changed', - { tone: selectedTone } - ); - } } - disabled={ isLoading } - allowCustom - /> - { - onPersonaChange( selectedPersona ); - } } - onDropdownChange={ ( selectedPersona ) => { - Telemetry.trackEvent( 'title_suggestions_ai_persona_changed', - { persona: selectedPersona } - ); - } } - disabled={ isLoading } - allowCustom - /> -
    - ) } +
    + { + onToneChange( selectedTone ); + } } + onDropdownChange={ ( selectedTone ) => { + Telemetry.trackEvent( 'title_suggestions_ai_tone_changed', + { tone: selectedTone } + ); + } } + disabled={ isLoading } + allowCustom + /> + { + onPersonaChange( selectedPersona ); + } } + onDropdownChange={ ( selectedPersona ) => { + Telemetry.trackEvent( 'title_suggestions_ai_persona_changed', + { persona: selectedPersona } + ); + } } + disabled={ isLoading } + allowCustom + />
    ); }; diff --git a/src/content-helper/editor-sidebar/title-suggestions/component-suggestions.tsx b/src/content-helper/editor-sidebar/title-suggestions/component-suggestions.tsx new file mode 100644 index 000000000..2d058fc1e --- /dev/null +++ b/src/content-helper/editor-sidebar/title-suggestions/component-suggestions.tsx @@ -0,0 +1,80 @@ +/** + * WordPress dependencies + */ +import { Panel, PanelBody, Spinner } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { Telemetry } from '../../../js/telemetry/telemetry'; +import { AiIcon } from '../../common/icons/ai-icon'; +import { TitleSuggestion } from './component-title-suggestion'; +import { Title, TitleType } from './store'; + +/** + * Props for the Title Suggestions component. + * + * @since 3.14.0 + */ +type TitleSuggestionsProps = { + suggestions: Title[]; + isOpen: boolean; + isLoading?: boolean; +}; + +/** + * Renders the Title Suggestions collapsible panel. + * + * @since 3.14.0 + * + * @param {TitleSuggestionsProps} props The component's props. + */ +export const TitleSuggestions = ( { + suggestions, + isOpen, + isLoading = false, +}: Readonly ): JSX.Element => { + const [ isCollapsed, setIsCollapsed ] = useState( isOpen ); + + /** + * Toggles the collapse state of the panel. + * + * @since 3.14.0 + */ + const toggleCollapse = () => { + setIsCollapsed( ! isCollapsed ); + Telemetry.trackEvent( 'title_suggestions_toggle', { + isOpen: ! isCollapsed, + + } ); + }; + + return ( + + } + onToggle={ toggleCollapse } + opened={ isCollapsed }> +
    + { isLoading && ( +
    + + { __( 'Loading…', 'wp-parsely' ) } +
    + ) } + { suggestions.map( ( title ) => ( + + ) ) } +
    +
    +
    + ); +}; diff --git a/src/content-helper/editor-sidebar/title-suggestions/component-title-suggestion.tsx b/src/content-helper/editor-sidebar/title-suggestions/component-title-suggestion.tsx index 3628f6d85..697100318 100644 --- a/src/content-helper/editor-sidebar/title-suggestions/component-title-suggestion.tsx +++ b/src/content-helper/editor-sidebar/title-suggestions/component-title-suggestion.tsx @@ -1,10 +1,23 @@ /** * WordPress dependencies */ -import { Button, ButtonGroup } from '@wordpress/components'; +import { + __experimentalHeading as Heading, + Button, + Modal, + Rect, + SVG, +} from '@wordpress/components'; import { dispatch, useDispatch, useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { check, closeSmall, pin, undo } from '@wordpress/icons'; +import { + check, + pin, + reset, + trash, + undo, +} from '@wordpress/icons'; /** * Internal dependencies @@ -24,6 +37,19 @@ interface TitleSuggestionProps { isOriginal?: boolean, } +/** + * Returns a vertical divider. + * + * @since 3.14.0 + */ +const VerticalDivider = (): JSX.Element => { + return ( + + + + ); +}; + /** * Renders a single title suggestion. * @@ -36,6 +62,10 @@ interface TitleSuggestionProps { export const TitleSuggestion = ( props: Readonly ): JSX.Element => { + const [ isModalOpen, setIsModalOpen ] = useState( false ); + const openModal = () => setIsModalOpen( true ); + const closeModal = () => setIsModalOpen( false ); + const { removeTitle, setAcceptedTitle, @@ -58,14 +88,43 @@ export const TitleSuggestion = ( // Flag if the current title has been accepted and applied to the post. const titleInUse = currentPostTitle === props.title.title; - const onClickAccept = async () => { + /** + * Handles the click event for the Apply button. + * + * @since 3.14.0 + */ + const onClickApply = () => { + if ( titleInUse ) { + return; + } + + openModal(); + }; + + /** + * Handles the click event for the Replace button. + * + * @since 3.14.0 + */ + const onClickReplace = async () => { + if ( titleInUse ) { + return; + } + Telemetry.trackEvent( 'title_suggestion_applied', { title: props.title.title, type: props.type, } ); await setAcceptedTitle( props.type, props.title ); + + closeModal(); }; + /** + * Handles the click event for the Pin button. + * + * @since 3.14.0 + */ const onClickPin = async () => { Telemetry.trackEvent( 'title_suggestion_pinned', { pinned: ! isPinned, @@ -79,6 +138,11 @@ export const TitleSuggestion = ( } }; + /** + * Handles the click event for the Remove button. + * + * @since 3.14.0 + */ const onClickRemove = async () => { Telemetry.trackEvent( 'title_suggestion_removed', { type: props.type, @@ -87,6 +151,11 @@ export const TitleSuggestion = ( await removeTitle( props.type, props.title ); }; + /** + * Handles the click event for the Restore button. + * + * @since 3.14.0 + */ const onClickRestore = async () => { Telemetry.trackEvent( 'title_suggestion_restored', { type: props.type, @@ -103,46 +172,76 @@ export const TitleSuggestion = ( return ( <> -
    -
    { props.title.title }
    -
    - { ( ! props.isOriginal ) ? ( - -
    + +
    + { isPinned ? ( +
    + + ) } +
    + { isModalOpen && ( + +
    +

    { props.title.title }

    + { __( + "You'll still be able to restore your original title until you exit the editor.", + 'wp-parsely', + ) } +
    + + +
    +
    +
    + ) } ); }; diff --git a/src/content-helper/editor-sidebar/title-suggestions/component.tsx b/src/content-helper/editor-sidebar/title-suggestions/component.tsx index f6e9fcd9e..71df08f1f 100644 --- a/src/content-helper/editor-sidebar/title-suggestions/component.tsx +++ b/src/content-helper/editor-sidebar/title-suggestions/component.tsx @@ -1,56 +1,48 @@ /** * WordPress dependencies */ -import { Button, PanelRow } from '@wordpress/components'; +import { Button, Notice, PanelRow } from '@wordpress/components'; import { dispatch, useDispatch, useSelect } from '@wordpress/data'; -import { createInterpolateElement, useState } from '@wordpress/element'; +import { createInterpolateElement, useEffect, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { Icon, external } from '@wordpress/icons'; /** * Internal dependencies */ import { GutenbergFunction } from '../../../@types/gutenberg/types'; import { Telemetry } from '../../../js/telemetry/telemetry'; -import { PersonaProp, getPersonaLabel } from '../../common/components/persona-selector'; -import { ToneProp, getToneLabel } from '../../common/components/tone-selector'; +import { getPersonaLabel, PersonaProp } from '../../common/components/persona-selector'; +import { getToneLabel, ToneProp } from '../../common/components/tone-selector'; import { ContentHelperError } from '../../common/content-helper-error'; -import { SidebarSettings } from '../editor-sidebar'; +import { SidebarSettings, useSettings } from '../../common/settings'; +import { PinnedTitleSuggestions } from './component-pinned'; import { TitleSuggestionsSettings } from './component-settings'; +import { TitleSuggestions } from './component-suggestions'; import { TitleSuggestion } from './component-title-suggestion'; -import { WriteTitleProvider } from './provider'; +import { TitleSuggestionsProvider } from './provider'; import { TitleStore, TitleType } from './store'; - -/** - * Defines the props structure for TitleSuggestionsPanel. - * - * @since 3.13.0 - */ -interface TitleSuggestionsPanelProps { - initialPersona: PersonaProp; - initialSettingsOpen: boolean; - initialTone: ToneProp; - onSettingChange: ( key: keyof SidebarSettings, value: string | boolean ) => void; -} +import './title-suggestions.scss'; /** * Title Suggestions Panel. * * @since 3.12.0 * - * @param {TitleSuggestionsPanelProps} props The component's props. - * * @return {JSX.Element} The Title Suggestions Panel. */ -export const TitleSuggestionsPanel = ( { - initialPersona, initialSettingsOpen, initialTone, onSettingChange, -}: TitleSuggestionsPanelProps ): JSX.Element => { +export const TitleSuggestionsPanel = (): JSX.Element => { + const { settings, setSettings } = useSettings(); + const [ error, setError ] = useState(); - const [ tone, setTone ] = useState( initialTone ); - const [ persona, setPersona ] = useState( initialPersona ); + const [ tone, setTone ] = useState( settings.TitleSuggestionsSettings.Tone ); + const [ persona, setPersona ] = useState( settings.TitleSuggestionsSettings.Persona ); const { loading, titles, + pinnedTitles, + allTitles, acceptedTitle, originalTitle, } = useSelect( ( select ) => { @@ -60,10 +52,15 @@ export const TitleSuggestionsPanel = ( { getOriginalTitle, } = select( TitleStore ); + // eslint-disable-next-line @typescript-eslint/no-shadow + const allTitles = getTitles( TitleType.PostTitle ); + return { acceptedTitle: getAcceptedTitle( TitleType.PostTitle ), loading: isLoading(), - titles: getTitles( TitleType.PostTitle ), + titles: allTitles.filter( ( title ) => ! title.isPinned ), + pinnedTitles: allTitles.filter( ( title ) => title.isPinned ), + allTitles, originalTitle: getOriginalTitle( TitleType.PostTitle ), }; }, [] ); @@ -75,6 +72,17 @@ export const TitleSuggestionsPanel = ( { setOriginalTitle, } = useDispatch( TitleStore ); + const { createNotice } = useDispatch( 'core/notices' ); + + const onSettingChange = ( key: keyof SidebarSettings[ 'TitleSuggestionsSettings' ], value: string | boolean ) => { + setSettings( { + TitleSuggestionsSettings: { + ...settings.TitleSuggestionsSettings, + [ key ]: value, + }, + } ); + }; + const currentPostContent = useSelect( ( select ) => { const { getEditedPostContent } = select( 'core/editor' ) as GutenbergFunction; return getEditedPostContent(); @@ -93,19 +101,21 @@ export const TitleSuggestionsPanel = ( { ): Promise => { await setLoading( true ); - const provider = new WriteTitleProvider(); + const provider = new TitleSuggestionsProvider(); try { const genTitles = await provider.generateTitles( content, 3, selectedTone, selectedPersona ); await setTitles( titleType, genTitles ); } catch ( err: any ) { // eslint-disable-line @typescript-eslint/no-explicit-any setError( err ); + setTitles( titleType, [] ); } await setLoading( false ); }; const generateOnClickHandler = async () => { + setError( undefined ); if ( false === loading ) { Telemetry.trackEvent( 'title_suggestions_generate_pressed', { request_more: titles.length > 0, @@ -125,126 +135,84 @@ export const TitleSuggestionsPanel = ( { } }; - const saveTitleOnClickHandler = async () => { + /** + * Handles the accepted title changing, and applies the accepted title to + * the post. + * + * @since 3.14.0 + */ + useEffect( () => { + if ( ! acceptedTitle ) { + return; + } + // Save the original title. - await setOriginalTitle( TitleType.PostTitle, currentPostTitle ); + setOriginalTitle( TitleType.PostTitle, currentPostTitle ); // Set the post title to the accepted title. dispatch( 'core/editor' ).editPost( { title: acceptedTitle?.title } ); // Pin the accepted title on the list of generated titles. if ( acceptedTitle ) { - await dispatch( TitleStore ).pinTitle( TitleType.PostTitle, acceptedTitle ); + dispatch( TitleStore ).pinTitle( TitleType.PostTitle, acceptedTitle ); Telemetry.trackEvent( 'title_suggestions_accept_pressed', { old_title: currentPostTitle, new_title: acceptedTitle.title, } ); } - // Remove the accepted title - await setAcceptedTitle( TitleType.PostTitle, undefined ); - }; + // Remove the accepted title. + setAcceptedTitle( TitleType.PostTitle, undefined ); - const parselyAISettings = { - onSettingChange( 'TitleSuggestionsPersona', selectedPersona ); - setPersona( selectedPersona ); - } } - onSettingChange={ onSettingChange } - onToneChange={ ( selectedTone ) => { - onSettingChange( 'TitleSuggestionsTone', selectedTone ); - setTone( selectedTone ); - } } - persona={ initialPersona } - tone={ initialTone } - />; + // Show snackbar notification. + createNotice( + 'success', + __( 'Title suggestion applied.', 'wp-parsely' ), + { + type: 'snackbar', + className: 'parsely-title-suggestion-applied', + explicitDismiss: true, + actions: [ + { + label: __( 'Undo', 'wp-parsely' ), + onClick: () => { + // Restore the original title. + dispatch( 'core/editor' ).editPost( { title: currentPostTitle } ); + setOriginalTitle( TitleType.PostTitle, undefined ); + }, + }, + ], + } + ); + }, [ acceptedTitle ] ); // eslint-disable-line react-hooks/exhaustive-deps - const generateTitleButton: JSX.Element = ( -
    - -
    - ); - - const titleSuggestionList: JSX.Element = ( -
    - { ( originalTitle !== undefined ) && ( - - ) } - - { titles.map( ( title ) => ( - - ) ) } -
    - ); - - const acceptedTitleElement: JSX.Element = ( -
    -
    - { __( - 'Replace the current post title with the following?', - 'wp-parsely' - ) } -
    -
    { acceptedTitle?.title }
    -
    - - -
    -
    - ); + /** + * Displays a snackbar notification when an error occurs. + * + * @since 3.14.0 + */ + useEffect( () => { + if ( undefined === error ) { + return; + } - if ( error ) { - return ( error.Message() ); - } + createNotice( + 'error', + __( 'There was an error generating title suggestions.', 'wp-parsely' ), + { + type: 'snackbar', + className: 'parsely-title-suggestion-error', + isDismissible: true, + } + ); + }, [ error ] ); // eslint-disable-line react-hooks/exhaustive-deps return ( -
    - { 0 === titles.length && acceptedTitle === undefined && ( - <> -
    - { __( - 'Use Parse.ly AI to generate a title for your post.', - 'wp-parsely' - ) } -
    - { parselyAISettings } - { generateTitleButton } - - ) } - { 0 < titles.length && acceptedTitle === undefined && ( - <> -
    +
    +
    + { allTitles.length > 0 ? ( + { createInterpolateElement( // translators: %1$s is the tone, %2$s is the persona. @@ -258,13 +226,80 @@ export const TitleSuggestionsPanel = ( { } ) } -
    - { titleSuggestionList } - { parselyAISettings } - { generateTitleButton } + + ) : ( + __( + 'Use Parse.ly AI to generate a title for your post.', + 'wp-parsely' + ) + ) } + +
    + { error && ( + + { error.message } + + ) } + { ( originalTitle !== undefined ) && ( + + ) } + { 0 < allTitles.length && ( + <> + { pinnedTitles.length > 0 && ( + + ) } + { titles.length > 0 && ( + + ) } ) } - { acceptedTitle !== undefined && ( acceptedTitleElement ) } + { + onSettingChange( 'Persona', selectedPersona ); + setPersona( selectedPersona ); + } } + onSettingChange={ onSettingChange } + onToneChange={ ( selectedTone ) => { + onSettingChange( 'Tone', selectedTone ); + setTone( selectedTone ); + } } + persona={ settings.TitleSuggestionsSettings.Persona } + tone={ settings.TitleSuggestionsSettings.Tone } + /> +
    + +
    ); diff --git a/src/content-helper/editor-sidebar/title-suggestions/provider.ts b/src/content-helper/editor-sidebar/title-suggestions/provider.ts index b882b2390..f30b9427b 100644 --- a/src/content-helper/editor-sidebar/title-suggestions/provider.ts +++ b/src/content-helper/editor-sidebar/title-suggestions/provider.ts @@ -16,22 +16,22 @@ import { getPersonaLabel, PersonaProp } from '../../common/components/persona-se /** * Specifies the form of the response returned by the - * `content-suggestions/write-title` WordPress REST API endpoint. + * `content-suggestions/suggest-headline` WordPress REST API endpoint. * * @since 3.12.0 */ -interface WriteTitleApiResponse { +interface SuggestHeadlineApiResponse { error?: Error; data: string[], } /** - * Returns data from the `content-suggestions/write-title` WordPress REST API + * Returns data from the `content-suggestions/suggest-headline` WordPress REST API * endpoint. * * @since 3.12.0 */ -export class WriteTitleProvider { +export class TitleSuggestionsProvider { /** * Returns a list of suggested titles for the given content. * @@ -46,13 +46,16 @@ export class WriteTitleProvider { let response; try { - response = await apiFetch( { - path: addQueryArgs( '/wp-parsely/v1/content-suggestions/write-title', { - content, + response = await apiFetch( { + method: 'POST', + path: addQueryArgs( '/wp-parsely/v1/content-suggestions/suggest-headline', { limit, tone: getToneLabel( tone ), persona: getPersonaLabel( persona ), - } ), + }, ), + data: { + content, + }, } ); } catch ( wpError: any ) { // eslint-disable-line @typescript-eslint/no-explicit-any return Promise.reject( new ContentHelperError( diff --git a/src/content-helper/editor-sidebar/title-suggestions/title-suggestions.scss b/src/content-helper/editor-sidebar/title-suggestions/title-suggestions.scss new file mode 100644 index 000000000..ecfbe31ac --- /dev/null +++ b/src/content-helper/editor-sidebar/title-suggestions/title-suggestions.scss @@ -0,0 +1,217 @@ +@import "../../common/css/variables"; +@import "../../common/css/functions"; + +// Title Suggestion Panel CSS. +.wp-parsely-content-helper .wp-parsely-title-suggestions-wrapper { + display: flex; + flex-direction: column; + + .title-suggestions-settings { + + display: flex; + padding: to_rem(6px) 0 var(--grid-unit-20, to_rem(16px)) 0; + flex-direction: column; + align-items: flex-start; + gap: var(--grid-unit-20, to_rem(16px)); + align-self: stretch; + + > div { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--grid-unit-10, to_rem(8px)); + align-self: stretch; + } + } + + .title-suggestions-header { + display: flex; + padding-bottom: var(--grid-unit-20, to_rem(16px)); + flex-direction: column; + align-items: flex-start; + gap: to_rem(10px); + align-self: stretch; + + .parsely-write-titles-text strong { + text-transform: lowercase; + } + } + + // Generate Titles button. + .title-suggestions-generate { + display: flex; + padding-top: to_rem(6px); + flex-direction: column; + align-items: flex-start; + align-self: stretch; + + .components-button { + height: to_rem(40px); + display: flex; + padding: var(--grid-unit-10, to_rem(8px)) var(--grid-unit-15, to_rem(12px)); + justify-content: center; + align-items: center; + align-self: stretch; + border-radius: 2px; + } + } + + .wp-parsely-dropdown-label { + align-self: stretch; + color: var(--sidebar-black, #1e1e1e); + font-size: var(--font-size-smaller, to_rem(11px)); + font-style: normal; + font-weight: 600; + line-height: var(--grid-unit-20, to_rem(16px)); + text-transform: uppercase; + } + + // Accepted Title view. + // TODO: Remove, unused? + .parsely-write-titles-accepted-title-container { + + .parsely-write-titles-accepted-title { + font-weight: 600; + font-size: to_rem(16px); + line-height: to_rem(20px); + margin: 0 0 to_rem(15px) 0; + text-align: center; + } + + // Accepted Title actions. + .parsely-write-titles-accepted-title-actions { + margin: to_rem(10px) 0; + justify-content: center; + display: flex; + gap: to_rem(10px); + } + } + + .wp-parsely-title-suggestion { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--grid-unit-10, to_rem(8px)); + align-self: stretch; + border-radius: 2px; + border: 1px solid var(--Gutenberg-Gray-400, #ccc); + + &.pinned-title { + background: var(--Gutenberg-Gray-100, #f0f0f0); + } + + .suggested-title { + color: #1e1e1e; + display: flex; + padding: + var(--grid-unit-15, to_rem(12px)) + var(--grid-unit-15, to_rem(12px)) + 0 + var(--grid-unit-15, to_rem(12px)); + flex-direction: column; + justify-content: center; + align-items: center; + gap: var(--grid-unit-10, to_rem(8px)); + align-self: stretch; + font-size: to_rem(12px); + font-style: normal; + font-weight: 600; + line-height: to_rem(20px); /* 142.857% */ + + .suggested-title-original { + align-self: flex-start; + margin: 0; + } + } + + .suggested-title-actions { + display: flex; + padding: 0 var(--grid-unit-10, to_rem(8px)); + align-items: center; + gap: var(--grid-unit-20, to_rem(16px)); + align-self: stretch; + flex-wrap: wrap; + border-top: 1px solid var(--Gutenberg-Gray-400, #ccc); + height: to_rem(40px); + + .suggested-title-actions-container { + display: flex; + flex-grow: 1; + gap: var(--grid-unit-10, to_rem(8px)); + align-items: center; + + .suggested-title-actions-left, + .suggested-title-actions-right { + display: flex; + gap: var(--grid-unit-10, to_rem(8px)); + } + } + } + } + + // Container for the title suggestions. + .title-suggestions-container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--grid-unit-20, to_rem(16px)); + align-self: stretch; + margin-bottom: var(--grid-unit-20, to_rem(16px)); + position: relative; + + .wp-parsely-loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: var(--grid-unit-10, to_rem(8px)); + background-color: rgba(255, 255, 255, 0.9); + z-index: 1; + + .components-spinner { + transform: scale(1.125); + } + } + } + + .wp-parsely-pinned-suggestions { + display: flex; + flex-direction: column; + align-items: flex-start; + align-self: stretch; + } + +} + +.wp-parsely-popover .components-popover__content { + width: to_rem(248px); +} + +.wp-parsely-suggested-title-modal { + display: flex; + width: to_rem(320px); + flex-direction: column; + align-items: flex-start; + + h2 { + color: var(--sidebar-black, #1e1e1e); + font-size: to_rem(16px); + font-style: normal; + font-weight: 600; + line-height: to_rem(24px); /* 150% */ + } + + .suggested-title-modal-actions { + display: flex; + margin-top: to_rem(24px); + justify-content: flex-end; + align-items: center; + gap: var(--grid-unit-15, to_rem(12px)); + align-self: stretch; + } +} diff --git a/src/content-helper/excerpt-generator/components/excerpt-panel.tsx b/src/content-helper/excerpt-generator/components/excerpt-panel.tsx index 34133375a..9b991c153 100644 --- a/src/content-helper/excerpt-generator/components/excerpt-panel.tsx +++ b/src/content-helper/excerpt-generator/components/excerpt-panel.tsx @@ -2,10 +2,10 @@ * WordPress dependencies */ import { + Animate, Button, - ExternalLink, + Icon, Notice, - Spinner, TextareaControl, } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; @@ -18,9 +18,9 @@ import { count } from '@wordpress/wordcount'; /** * Internal dependencies */ +import { external } from '@wordpress/icons'; import { GutenbergFunction } from '../../../@types/gutenberg/types'; import { Telemetry } from '../../../js/telemetry/telemetry'; -import { BetaBadge } from '../../common/components/beta-badge'; import { ContentHelperError } from '../../common/content-helper-error'; import { LeafIcon } from '../../common/icons/leaf-icon'; import { ExcerptGeneratorProvider } from '../provider'; @@ -33,6 +33,7 @@ import { ExcerptGeneratorProvider } from '../provider'; const PostExcerptGenerator = () => { const [ isLoading, setLoading ] = useState( false ); const [ generatedExcerpt, setGeneratedExcerpt ] = useState( '' ); + const [ generatedExcerptCount, setGeneratedExcerptCount ] = useState( 0 ); const [ error, setError ] = useState(); const { editPost } = useDispatch( editorStore ); @@ -84,6 +85,7 @@ const PostExcerptGenerator = () => { const generateExcerpt = async () => { setLoading( true ); setError( undefined ); + setGeneratedExcerptCount( generatedExcerptCount + 1 ); try { Telemetry.trackEvent( 'excerpt_generator_pressed' ); @@ -134,8 +136,8 @@ const PostExcerptGenerator = () => {
    { isLoading && ( -
    0 ? ' has-word-count' : '' ) }> - +
    +
    ) } { label={ __( 'Write an excerpt (optional)', 'wp-parsely' ) } className="editor-post-excerpt__textarea" onChange={ ( value ) => editPost( { excerpt: value } ) } - disabled={ isLoading || hasGeneratedExcerpt } - value={ getExcerptTextareaValue() } + readOnly={ isLoading || hasGeneratedExcerpt } + value={ isLoading ? '' : getExcerptTextareaValue() } help={ wordCount ? wordCountString : null } />
    - { __( 'Learn more about manual excerpts', 'wp-parsely' ) } - - + +
    - +
    - { __( 'Parse.ly AI', 'wp-parsely' ) } + { __( 'Generate With Parse.ly', 'wp-parsely' ) } + { __( 'Beta', 'wp-parsely' ) }
    -
    { error && ( { isBusy={ isLoading } disabled={ isLoading } > - { isLoading ? __( 'Generating…', 'wp-parsely' ) - : __( 'Generate Excerpt', 'wp-parsely' ) } + { isLoading && __( 'Generating Excerpt…', 'wp-parsely' ) } + { ! isLoading && generatedExcerptCount > 0 && __( 'Regenerate Excerpt', 'wp-parsely' ) } + { ! isLoading && generatedExcerptCount === 0 && __( 'Generate Excerpt', 'wp-parsely' ) } ) }
    +
    ); }; +/** + * Component that renders a loading animation. + * + * @since 3.14.0 + * + * @return {JSX.Element} The loading animation component. + */ +const LoadingAnimation = (): JSX.Element => { + return ( + + { ( { className } ) => ( + + { __( 'Generating…', 'wp-parsely' ) } + + ) } + + ); +}; + /** * The ExcerptPanel component verifies that the current post type supports excerpts, * and then renders the PostExcerptGenerator component. diff --git a/src/content-helper/excerpt-generator/excerpt-generator.scss b/src/content-helper/excerpt-generator/excerpt-generator.scss index c1a33095d..3583de591 100644 --- a/src/content-helper/excerpt-generator/excerpt-generator.scss +++ b/src/content-helper/excerpt-generator/excerpt-generator.scss @@ -1,55 +1,55 @@ @import "../common/css/variables"; @import "../common/css/functions"; -.editor-post-excerpt__spinner { +.editor-post-excerpt__loading_animation { position: absolute; - top: to_rem(20px); - left: 0; - right: 0; - bottom: 0; - display: flex; - justify-content: center; - align-items: center; - background: rgba(255, 255, 255, 0.7); // semi-transparent white background. - z-index: 10; + top: to_rem(29px); + left: to_rem(9px); } -.editor-post-excerpt__spinner.has-word-count { - bottom: to_rem(20px); +.editor-post-excerpt__textarea { + margin-bottom: var(--grid-unit-10, to_rem(8px)); + + textarea:read-only { + background-color: var(--Gutenberg-White, #fff); + } } .wp-parsely-excerpt-generator { - margin-top: to_rem(15px); + margin-top: to_rem(20px); .wp-parsely-excerpt-generator-header { align-items: center; display: flex; justify-content: flex-start; - margin-bottom: to_rem(10px); .wp-parsely-excerpt-generator-header-label { font-size: to_rem(11px); - font-weight: 500; + font-weight: 600; line-height: 1.4; text-transform: uppercase; display: inline-block; padding: 0; margin-left: to_rem(5px); + + span.beta-label { + padding-left: to_rem(6px); + color: var(--Gutenberg-Gray-700, #757575); + } } } .wp-parsely-excerpt-generator-controls { display: flex; - justify-content: center; - align-items: center; - gap: to_rem(15px); - } - - .wp-parsely-excerpt-generator-error { - margin: 0; - - p { - margin: 0; + gap: var(--grid-unit-10, to_rem(8px)); + + button { + flex-grow: 1; + height: to_rem(40px); + justify-content: center; + margin-bottom: var(--grid-unit-10, to_rem(8px)); + margin-top: var(--grid-unit-10, to_rem(8px)); + padding: var(--grid-unit-10, to_rem(8px)) var(--grid-unit-15, to_rem(12px)); } } } diff --git a/src/content-helper/excerpt-generator/provider.ts b/src/content-helper/excerpt-generator/provider.ts index 255856520..75b4e39ab 100644 --- a/src/content-helper/excerpt-generator/provider.ts +++ b/src/content-helper/excerpt-generator/provider.ts @@ -11,7 +11,7 @@ import { ContentHelperError, ContentHelperErrorCode } from '../common/content-he /** * Specifies the form of the response returned by the - * `/content-suggestions/suggest-meta-description` WordPress REST API endpoint. + * `/content-suggestions/suggest-brief` WordPress REST API endpoint. * * @since 3.13.0 */ @@ -38,10 +38,13 @@ export class ExcerptGeneratorProvider { let response; try { response = await apiFetch( { - path: addQueryArgs( '/wp-parsely/v1/content-suggestions/suggest-meta-description', { + method: 'POST', + path: addQueryArgs( '/wp-parsely/v1/content-suggestions/suggest-brief', { title, - content, } ), + data: { + content, + }, } ); } catch ( wpError: any ) { // eslint-disable-line @typescript-eslint/no-explicit-any return Promise.reject( new ContentHelperError( wpError.message, wpError.code ) ); diff --git a/src/content-helper/post-list-stats/class-post-list-stats.php b/src/content-helper/post-list-stats/class-post-list-stats.php index 401e31d49..76e8c3c86 100644 --- a/src/content-helper/post-list-stats/class-post-list-stats.php +++ b/src/content-helper/post-list-stats/class-post-list-stats.php @@ -123,7 +123,7 @@ public function run(): void { if ( ! $this->can_enable_feature( $this->parsely->site_id_is_set(), $this->parsely->api_secret_is_set(), - $this->analytics_api->is_user_allowed_to_make_api_call() + $this->analytics_api->is_available_to_current_user() ) ) { return; } diff --git a/src/js/telemetry/block-change.tsx b/src/js/telemetry/block-change.tsx index 8b27a05c0..5d89590a7 100644 --- a/src/js/telemetry/block-change.tsx +++ b/src/js/telemetry/block-change.tsx @@ -1,83 +1,120 @@ /** * WordPress dependencies */ -// eslint-disable-next-line import/named -import { BlockInstance } from '@wordpress/blocks'; -import { select, subscribe } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; +import { select, subscribe } from '@wordpress/data'; + +/** + * External dependencies + */ +// eslint-disable-next-line import/no-extraneous-dependencies +import debounce from 'lodash/debounce'; /** * Internal dependencies */ +import { Telemetry } from './telemetry'; /** - * BlockChangeMonitor component. - * - * This is a React component that monitors changes in the WordPress block editor. - * It does not render anything, but it uses the useEffect hook to subscribe to changes in the block editor - * when the component is mounted. - * When the block editor changes, it checks if blocks have been added or removed by comparing the current - * list of blocks with the previous one. - * If a block has been added or removed, it sends a telemetry event to the server. - * When the component is unmounted, it unsubscribes from the block editor changes. + * BlockChangeMonitor is a React component that monitors changes in the WordPress block editor. + * It tracks the addition and removal of blocks that start with the 'wp-parsely/' prefix. + * This component does not render anything. * * @since 3.12.0 + * + * @return {null} This component does not render anything. */ -export const BlockChangeMonitor = () => { +export const BlockChangeMonitor = (): null => { /** - * The prefix of the block's name. + * The prefix of the blocks to monitor. * * @since 3.12.0 */ const parselyBlockPrefix: string = 'wp-parsely/'; /** - * The useEffect hook is used to subscribe to changes in the block editor when the component is mounted. - * It first gets the current list of blocks and creates a Set of the block IDs. - * Then, it subscribes to changes in the block editor. - * When the block editor changes, it gets the new list of blocks and creates a new Set of the block IDs. - * It checks if the size of the new block IDs Set is different from the last one, indicating that a block - * has been added or removed. + * This useEffect hook is used to monitor changes in the WordPress block editor. + * It delays the initialization to avoid reacting to the initial block load. + * It subscribes to changes in the block editor when the component is mounted. + * When the block editor changes, it checks if blocks have been added or removed by comparing the current + * list of blocks with the previous one. * If a block has been added or removed, it sends a telemetry event to the server. - * Finally, it updates the last block IDs with the new block IDs for the next comparison. - * When the component is unmounted, it unsubscribes from the block editor changes. + * When the component is unmounted, it clears the initialization timeout. * * @since 3.12.0 + * @since 3.14.0 Improved detection by comparing current and previous block states directly. */ useEffect( () => { - const getBlockList = () => select( 'core/block-editor' ).getBlocks() as BlockInstance[]; - let lastBlockIds = new Set( getBlockList().map( ( block ) => block.clientId ) ); + // Debounce interval to save CPU cycles with the frequent editor updates. + const debounceInterval = 1000; // In milliseconds. - const unsubscribe = subscribe( () => { - const newBlockList = getBlockList(); - const newBlockIds = new Set( newBlockList.map( ( block ) => block.clientId ) ); + let unsubscribe: () => void; + const initialize = () => { + let previousBlocks = select( 'core/block-editor' ).getBlocks(); - if ( newBlockIds.size !== lastBlockIds.size ) { - const blocksAdded = newBlockIds.size > lastBlockIds.size; - const changedBlockList = blocksAdded ? newBlockList : Array.from( lastBlockIds ); + /** + * Checks if blocks have been added or removed and sends telemetry events accordingly. + * + * @since 3.14.0 + */ + const checkBlocks = () => { + const currentBlocks = select( 'core/block-editor' ).getBlocks(); + const currentBlockIds = currentBlocks.map( ( block ) => block.clientId ); + const previousBlockIds = previousBlocks.map( ( block ) => block.clientId ); - for ( const block of changedBlockList ) { - if ( blocksAdded ) { - // block is a BlockInstance when blocks are added. - const blockInstance = block as BlockInstance; - if ( blockInstance.name.startsWith( parselyBlockPrefix ) && ! lastBlockIds.has( blockInstance.clientId ) ) { - // Telemetry.trackEvent( 'block_added', { block: blockInstance.name } ); - } - } else { - // block is a string (client ID) when blocks are removed. - const clientId = block as string; - if ( ! newBlockIds.has( clientId ) ) { - // Telemetry.trackEvent( 'block_removed', { block: clientId } ); - } + // Find added blocks. + const addedBlocks = currentBlocks.filter( + ( block ) => ! previousBlockIds.includes( block.clientId ), + ); + addedBlocks.forEach( ( block ) => { + if ( block.name.startsWith( parselyBlockPrefix ) ) { + Telemetry.trackEvent( 'block_added', { block: block.name } ); } - } - } + } ); - lastBlockIds = newBlockIds; - } ); + // Find removed blocks. + const removedBlockIds = previousBlockIds.filter( ( id ) => ! currentBlockIds.includes( id ) ); + removedBlockIds.forEach( ( id ) => { + const removedBlock = previousBlocks.find( ( block ) => block.clientId === id ); + if ( removedBlock && removedBlock.name.startsWith( parselyBlockPrefix ) ) { + Telemetry.trackEvent( 'block_removed', { block: removedBlock.name } ); + } + } ); + + // Update the previousBlocks for the next check. + previousBlocks = currentBlocks; + }; + + // Debounce the checkBlocks function to save CPU cycles with the frequent editor updates. + const debouncedCheckBlocks = debounce( checkBlocks, debounceInterval ); + unsubscribe = subscribe( debouncedCheckBlocks, 'core/block-editor' ); + return unsubscribe; + }; + + /** + * Checks if the editor is ready to be monitored. + * It waits for the editor to be clean or to have at least one block, and it resolves when it's ready. + * + * @since 3.14.0 + */ + const isEditorReady = async (): Promise => { + return new Promise( ( resolve ) => { + const unsubscribeEditorReady = subscribe( () => { + if ( select( 'core/editor' ).isCleanNewPost() || select( 'core/block-editor' ).getBlockCount() > 0 ) { + unsubscribeEditorReady(); + resolve(); + } + } ); + } ); + }; + + // Initialize the block change monitor when the editor is ready. + isEditorReady().then( initialize ); return () => { - unsubscribe(); + if ( unsubscribe ) { + unsubscribe(); + } }; }, [] ); diff --git a/tests/Integration/ContentHelperFeatureTest.php b/tests/Integration/ContentHelperFeatureTest.php index 520add702..b250e2f86 100644 --- a/tests/Integration/ContentHelperFeatureTest.php +++ b/tests/Integration/ContentHelperFeatureTest.php @@ -105,7 +105,6 @@ function () use ( $feature_filter_value ) { * @covers \Parsely\Content_Helper\Post_List_Stats::is_tracked_as_post_type * @covers \Parsely\Content_Helper\Post_List_Stats::run * @covers \Parsely\Content_Helper\Post_List_Stats::set_current_screen - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -149,7 +148,6 @@ public function test_assets_get_enqueued_by_default(): void { * @covers \Parsely\Content_Helper\Post_List_Stats::is_tracked_as_post_type * @covers \Parsely\Content_Helper\Post_List_Stats::run * @covers \Parsely\Content_Helper\Post_List_Stats::set_current_screen - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -186,7 +184,6 @@ public function test_assets_get_enqueued_when_global_filter_is_true(): void { * @covers \Parsely\Content_Helper\Post_List_Stats::__construct * @covers \Parsely\Content_Helper\Post_List_Stats::get_feature_filter_name * @covers \Parsely\Content_Helper\Post_List_Stats::run - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -223,7 +220,6 @@ public function test_assets_do_not_get_enqueued_when_global_filter_is_false(): v * @covers \Parsely\Content_Helper\Post_List_Stats::__construct * @covers \Parsely\Content_Helper\Post_List_Stats::get_feature_filter_name * @covers \Parsely\Content_Helper\Post_List_Stats::run - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -268,7 +264,6 @@ public function test_assets_do_not_get_enqueued_when_global_filter_is_invalid(): * @covers \Parsely\Content_Helper\Post_List_Stats::is_tracked_as_post_type * @covers \Parsely\Content_Helper\Post_List_Stats::run * @covers \Parsely\Content_Helper\Post_List_Stats::set_current_screen - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -305,7 +300,6 @@ public function test_assets_get_enqueued_when_feature_filter_is_true(): void { * @covers \Parsely\Content_Helper\Post_List_Stats::__construct * @covers \Parsely\Content_Helper\Post_List_Stats::get_feature_filter_name * @covers \Parsely\Content_Helper\Post_List_Stats::run - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -342,7 +336,6 @@ public function test_assets_do_not_get_enqueued_when_feature_filter_is_false(): * @covers \Parsely\Content_Helper\Post_List_Stats::__construct * @covers \Parsely\Content_Helper\Post_List_Stats::get_feature_filter_name * @covers \Parsely\Content_Helper\Post_List_Stats::run - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -386,7 +379,6 @@ public function test_assets_do_not_get_enqueued_when_feature_filter_is_invalid() * @covers \Parsely\Content_Helper\Post_List_Stats::is_tracked_as_post_type * @covers \Parsely\Content_Helper\Post_List_Stats::run * @covers \Parsely\Content_Helper\Post_List_Stats::set_current_screen - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -430,7 +422,6 @@ public function test_assets_get_enqueued_when_both_filters_are_true(): void { * @covers \Parsely\Content_Helper\Post_List_Stats::is_tracked_as_post_type * @covers \Parsely\Content_Helper\Post_List_Stats::run * @covers \Parsely\Content_Helper\Post_List_Stats::set_current_screen - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -467,7 +458,6 @@ public function test_assets_do_not_get_enqueued_when_both_filters_are_false(): v * @covers \Parsely\Content_Helper\Post_List_Stats::__construct * @covers \Parsely\Content_Helper\Post_List_Stats::get_feature_filter_name * @covers \Parsely\Content_Helper\Post_List_Stats::run - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -504,7 +494,6 @@ public function test_assets_do_not_get_enqueued_when_both_filters_are_invalid(): * @covers \Parsely\Content_Helper\Post_List_Stats::__construct * @covers \Parsely\Content_Helper\Post_List_Stats::get_feature_filter_name * @covers \Parsely\Content_Helper\Post_List_Stats::run - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -548,7 +537,6 @@ public function test_assets_do_not_get_enqueued_when_global_filter_is_true_and_f * @covers \Parsely\Content_Helper\Post_List_Stats::is_tracked_as_post_type * @covers \Parsely\Content_Helper\Post_List_Stats::run * @covers \Parsely\Content_Helper\Post_List_Stats::set_current_screen - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -586,7 +574,6 @@ public function test_assets_get_enqueued_when_global_filter_is_false_and_feature * @covers \Parsely\Content_Helper\Post_List_Stats::__construct * @covers \Parsely\Content_Helper\Post_List_Stats::get_feature_filter_name * @covers \Parsely\Content_Helper\Post_List_Stats::run - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options @@ -630,7 +617,6 @@ public function test_assets_do_not_get_enqueued_when_global_filter_is_true_and_f * @covers \Parsely\Content_Helper\Post_List_Stats::is_tracked_as_post_type * @covers \Parsely\Content_Helper\Post_List_Stats::run * @covers \Parsely\Content_Helper\Post_List_Stats::set_current_screen - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set * @uses \Parsely\Parsely::get_options diff --git a/tests/Integration/Endpoints/AnalyticsPostsProxyEndpointTest.php b/tests/Integration/Endpoints/AnalyticsPostsProxyEndpointTest.php index 5ca71074d..19d6170ff 100644 --- a/tests/Integration/Endpoints/AnalyticsPostsProxyEndpointTest.php +++ b/tests/Integration/Endpoints/AnalyticsPostsProxyEndpointTest.php @@ -73,7 +73,7 @@ public function test_verify_that_route_is_not_registered_when_proxy_is_disabled( * Verifies forbidden error when current user doesn't have proper * capabilities. * - * @covers \Parsely\Endpoints\Base_API_Proxy::permission_callback + * @covers \Parsely\RemoteAPI\Analytics_Posts_API::is_available_to_current_user * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::register_endpoint */ @@ -103,7 +103,7 @@ public function test_access_of_analytics_posts_endpoint_is_forbidden(): void { * * @covers \Parsely\Endpoints\Analytics_Posts_API_Proxy::get_items * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::__construct - * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::permission_callback + * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::run * @uses \Parsely\Parsely::site_id_is_missing * @uses \Parsely\Parsely::site_id_is_set @@ -124,7 +124,7 @@ public function test_get_items_fails_when_site_id_is_not_set(): void { * * @covers \Parsely\Endpoints\Analytics_Posts_API_Proxy::get_items * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::__construct - * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::permission_callback + * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::run * @uses \Parsely\Parsely::site_id_is_missing * @uses \Parsely\Parsely::site_id_is_set @@ -142,9 +142,9 @@ public function test_get_items_fails_when_api_secret_is_not_set(): void { /** * Verifies default user capability filter. * - * @covers \Parsely\Endpoints\Analytics_Posts_API_Proxy::permission_callback + * @covers \Parsely\Endpoints\Analytics_Posts_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call + * @uses \Parsely\RemoteAPI\Analytics_Posts_API::is_available_to_current_user */ public function test_user_is_allowed_to_make_proxy_api_call_if_default_user_capability_is_changed(): void { parent::run_test_user_is_allowed_to_make_proxy_api_call_if_default_user_capability_is_changed(); @@ -153,9 +153,9 @@ public function test_user_is_allowed_to_make_proxy_api_call_if_default_user_capa /** * Verifies endpoint specific user capability filter. * - * @covers \Parsely\Endpoints\Analytics_Posts_API_Proxy::permission_callback + * @covers \Parsely\Endpoints\Analytics_Posts_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call + * @uses \Parsely\RemoteAPI\Analytics_Posts_API::is_available_to_current_user */ public function test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific_user_capability_is_changed(): void { parent::run_test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific_user_capability_is_changed( 'analytics_posts' ); @@ -168,7 +168,7 @@ public function test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific * @covers \Parsely\Endpoints\Analytics_Posts_API_Proxy::get_items * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::__construct * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::generate_data - * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::permission_callback + * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Analytics_Posts_API_Proxy::run * @uses \Parsely\Endpoints\Base_API_Proxy::get_data * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint @@ -242,6 +242,7 @@ function () use ( &$dispatched ): array { 'url' => 'https://blog.parse.ly/web-analytics-software-tools/?itm_source=wp-parsely-content-helper', 'views' => '142', 'postId' => 0, + 'rawUrl' => 'https://blog.parse.ly/web-analytics-software-tools/', ), (object) array( 'author' => 'Stephanie Schwartz and Andrew Butler', @@ -253,6 +254,7 @@ function () use ( &$dispatched ): array { 'url' => 'https://blog.parse.ly/5-tagging-best-practices-content-strategy/?itm_source=wp-parsely-content-helper', 'views' => '40', 'postId' => 0, + 'rawUrl' => 'https://blog.parse.ly/5-tagging-best-practices-content-strategy/', ), ), ), diff --git a/tests/Integration/Endpoints/DashboardWidgetSettingsEndpointTest.php b/tests/Integration/Endpoints/DashboardWidgetSettingsEndpointTest.php index 390dee873..401781bf1 100644 --- a/tests/Integration/Endpoints/DashboardWidgetSettingsEndpointTest.php +++ b/tests/Integration/Endpoints/DashboardWidgetSettingsEndpointTest.php @@ -66,11 +66,10 @@ public function get_endpoint(): Base_Endpoint_User_Meta { * @covers \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::get_route * @covers \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::run * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * @uses \Parsely\Endpoints\Base_Endpoint::permission_callback * @uses \Parsely\Endpoints\Base_Endpoint::register_endpoint * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::__construct * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::get_subvalues_specs + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::is_available_to_current_user * @uses \Parsely\Endpoints\User_Meta\Dashboard_Widget_Settings_Endpoint::get_subvalues_specs * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::allow_parsely_remote_requests @@ -106,52 +105,10 @@ public function test_register_routes_by_default(): void { * @uses \Parsely\Parsely::set_managed_options * @uses \Parsely\Utils\convert_endpoint_to_filter_key */ - public function test_verify_that_route_is_not_registered_when_proxy_is_disabled(): void { + public function test_verify_that_route_is_not_registered_when_endpoint_is_disabled(): void { parent::run_test_do_not_register_route_when_proxy_is_disabled(); } - /** - * Verifies default user capability filter. - * - * @since 3.13.0 - * - * @covers \Parsely\Endpoints\Base_Endpoint::permission_callback - * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::__construct - * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::get_subvalues_specs - * @uses \Parsely\Endpoints\User_Meta\Dashboard_Widget_Settings_Endpoint::get_subvalues_specs - * @uses \Parsely\Parsely::__construct - * @uses \Parsely\Parsely::allow_parsely_remote_requests - * @uses \Parsely\Parsely::are_credentials_managed - * @uses \Parsely\Parsely::set_managed_options - * @uses \Parsely\Utils\convert_endpoint_to_filter_key - */ - public function test_user_is_allowed_to_make_proxy_api_call_if_default_user_capability_is_changed(): void { - parent::run_test_user_is_allowed_to_make_proxy_api_call_if_default_user_capability_is_changed(); - } - - /** - * Verifies endpoint specific user capability filter. - * - * @since 3.13.0 - * - * @covers \Parsely\Endpoints\Base_Endpoint::permission_callback - * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::__construct - * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::get_subvalues_specs - * @uses \Parsely\Endpoints\User_Meta\Dashboard_Widget_Settings_Endpoint::get_subvalues_specs - * @uses \Parsely\Parsely::__construct - * @uses \Parsely\Parsely::allow_parsely_remote_requests - * @uses \Parsely\Parsely::are_credentials_managed - * @uses \Parsely\Parsely::set_managed_options - * @uses \Parsely\Utils\convert_endpoint_to_filter_key - */ - public function test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific_user_capability_is_changed(): void { - parent::run_test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific_user_capability_is_changed(); - } - /** * Verifies that the endpoint returns the correct default settings. * @@ -163,9 +120,8 @@ public function test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific * @covers \Parsely\Endpoints\User_Meta\Dashboard_Widget_Settings_Endpoint::get_subvalues_specs * @covers \Parsely\Endpoints\User_Meta\Dashboard_Widget_Settings_Endpoint::process_request * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * @uses \Parsely\Endpoints\Base_Endpoint::permission_callback * @uses \Parsely\Endpoints\Base_Endpoint::register_endpoint + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::is_available_to_current_user * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::run * @uses \Parsely\Endpoints\User_Meta\Dashboard_Widget_Settings_Endpoint::get_meta_key * @uses \Parsely\Endpoints\User_Meta\Dashboard_Widget_Settings_Endpoint::get_route @@ -195,10 +151,9 @@ public function test_endpoint_returns_value_on_get_request(): void { * @covers \Parsely\Endpoints\User_Meta\Dashboard_Widget_Settings_Endpoint::get_subvalues_specs * @covers \Parsely\Endpoints\User_Meta\Dashboard_Widget_Settings_Endpoint::process_request * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * @uses \Parsely\Endpoints\Base_Endpoint::permission_callback * @uses \Parsely\Endpoints\Base_Endpoint::register_endpoint * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::__construct + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::is_available_to_current_user * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::run * @uses \Parsely\Endpoints\User_Meta\Dashboard_Widget_Settings_Endpoint::get_meta_key * @uses \Parsely\Endpoints\User_Meta\Dashboard_Widget_Settings_Endpoint::get_route diff --git a/tests/Integration/Endpoints/EditorSidebarSettingsEndpointTest.php b/tests/Integration/Endpoints/EditorSidebarSettingsEndpointTest.php index b06fed665..8f06a58a0 100644 --- a/tests/Integration/Endpoints/EditorSidebarSettingsEndpointTest.php +++ b/tests/Integration/Endpoints/EditorSidebarSettingsEndpointTest.php @@ -31,17 +31,25 @@ final class EditorSidebarSettingsEndpointTest extends BaseUserMetaEndpointTest { * @var array */ protected $default_value = array( - 'PerformanceDetailsOpen' => true, - 'RelatedTopPostsFilterBy' => 'unavailable', - 'RelatedTopPostsFilterValue' => '', - 'RelatedTopPostsOpen' => false, - 'SettingsMetric' => 'views', - 'SettingsOpen' => true, - 'SettingsPeriod' => '7d', - 'TitleSuggestionsOpen' => false, - 'TitleSuggestionsPersona' => 'journalist', - 'TitleSuggestionsSettingsOpen' => false, - 'TitleSuggestionsTone' => 'neutral', + 'InitialTabName' => 'tools', + 'PerformanceStatsSettings' => array( + 'Period' => '7d', + 'VisiblePanels' => array( 'overview', 'categories', 'referrers' ), + 'VisibleDataPoints' => array( 'views', 'visitors', 'avgEngaged', 'recirculation' ), + ), + 'RelatedPostsFilterBy' => 'unavailable', + 'RelatedPostsFilterValue' => '', + 'RelatedPostsMetric' => 'views', + 'RelatedPostsOpen' => false, + 'RelatedPostsPeriod' => '7d', + 'SmartLinkingMaxLinks' => 10, + 'SmartLinkingMaxLinkWords' => 4, + 'SmartLinkingOpen' => false, + 'TitleSuggestionsSettings' => array( + 'Open' => false, + 'Persona' => 'journalist', + 'Tone' => 'neutral', + ), ); /** @@ -75,11 +83,10 @@ public function get_endpoint(): Base_Endpoint_User_Meta { * @covers \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::get_route * @covers \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::run * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * @uses \Parsely\Endpoints\Base_Endpoint::permission_callback * @uses \Parsely\Endpoints\Base_Endpoint::register_endpoint * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::__construct * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::get_subvalues_specs + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::is_available_to_current_user * @uses \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::get_subvalues_specs * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::allow_parsely_remote_requests @@ -115,52 +122,10 @@ public function test_register_routes_by_default(): void { * @uses \Parsely\Parsely::set_managed_options * @uses \Parsely\Utils\convert_endpoint_to_filter_key */ - public function test_verify_that_route_is_not_registered_when_proxy_is_disabled(): void { + public function test_verify_that_route_is_not_registered_when_endpoint_is_disabled(): void { parent::run_test_do_not_register_route_when_proxy_is_disabled(); } - /** - * Verifies default user capability filter. - * - * @since 3.13.0 - * - * @covers \Parsely\Endpoints\Base_Endpoint::permission_callback - * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::__construct - * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::get_subvalues_specs - * @uses \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::get_subvalues_specs - * @uses \Parsely\Parsely::__construct - * @uses \Parsely\Parsely::allow_parsely_remote_requests - * @uses \Parsely\Parsely::are_credentials_managed - * @uses \Parsely\Parsely::set_managed_options - * @uses \Parsely\Utils\convert_endpoint_to_filter_key - */ - public function test_user_is_allowed_to_make_proxy_api_call_if_default_user_capability_is_changed(): void { - parent::run_test_user_is_allowed_to_make_proxy_api_call_if_default_user_capability_is_changed(); - } - - /** - * Verifies endpoint specific user capability filter. - * - * @since 3.13.0 - * - * @covers \Parsely\Endpoints\Base_Endpoint::permission_callback - * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::__construct - * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::get_subvalues_specs - * @uses \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::get_subvalues_specs - * @uses \Parsely\Parsely::__construct - * @uses \Parsely\Parsely::allow_parsely_remote_requests - * @uses \Parsely\Parsely::are_credentials_managed - * @uses \Parsely\Parsely::set_managed_options - * @uses \Parsely\Utils\convert_endpoint_to_filter_key - */ - public function test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific_user_capability_is_changed(): void { - parent::run_test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific_user_capability_is_changed(); - } - /** * Verifies that the endpoint returns the correct default settings. * @@ -172,9 +137,8 @@ public function test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific * @covers \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::get_subvalues_specs * @covers \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::process_request * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * @uses \Parsely\Endpoints\Base_Endpoint::permission_callback * @uses \Parsely\Endpoints\Base_Endpoint::register_endpoint + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::is_available_to_current_user * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::run * @uses \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::get_meta_key * @uses \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::get_route @@ -204,10 +168,9 @@ public function test_endpoint_returns_value_on_get_request(): void { * @covers \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::get_subvalues_specs * @covers \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::process_request * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * @uses \Parsely\Endpoints\Base_Endpoint::permission_callback * @uses \Parsely\Endpoints\Base_Endpoint::register_endpoint * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::__construct + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::is_available_to_current_user * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::run * @uses \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::get_meta_key * @uses \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::get_route @@ -227,33 +190,91 @@ public function test_endpoint_correctly_handles_put_requests( self::assertSame( $expected, $value ); } + /** + * Tests that the endpoint can correctly handle PUT requests with valid + * nested PerformanceStatsSettings values. + * + * @since 3.14.0 + * + * @covers \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::sanitize_subvalue + * @uses \Parsely\Endpoints\Base_Endpoint::__construct() + * @uses \Parsely\Endpoints\Base_Endpoint::register_endpoint() + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::__construct() + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::get_route() + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::get_value() + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::is_available_to_current_user() + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::process_request() + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::run() + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::sanitize_value() + * @uses \Parsely\Endpoints\User_Meta\Base_Endpoint_User_Meta::set_value() + * @uses \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::get_meta_key() + * @uses \Parsely\Endpoints\User_Meta\Editor_Sidebar_Settings_Endpoint::get_subvalues_specs() + * @uses \Parsely\Parsely::__construct() + * @uses \Parsely\Parsely::allow_parsely_remote_requests() + * @uses \Parsely\Parsely::are_credentials_managed() + * @uses \Parsely\Parsely::set_managed_options() + * @uses \Parsely\Utils\convert_endpoint_to_filter_key() + */ + public function test_valid_nested_performance_stats_settings_period(): void { + $this->set_admin_user(); + + $value = $this->send_put_request( + $this->generate_json( + 'views', + '7d', + array( + 'PerformanceStatsSettings' => array( + 'Period' => '1h', + 'VisiblePanels' => array( 'overview', 'referrers' ), + 'VisibleDataPoints' => array( 'views', 'avgEngaged', 'recirculation' ), + ), + ) + ) + ); + + $expected = $this->wp_json_encode( + array_merge( + $this->default_value, + array( + 'PerformanceStatsSettings' => array( + 'Period' => '1h', + 'VisiblePanels' => array( 'overview', 'referrers' ), + 'VisibleDataPoints' => array( 'views', 'avgEngaged', 'recirculation' ), + ), + ) + ) + ); + + self::assertSame( $expected, $value ); + } + /** * Generates a JSON string for the passed period, metric, and extra data. * * @since 3.13.0 * - * @param string|null $settings_metric The SettingsMetric value. - * @param string|null $settings_period The SettingsPeriod value. + * @param string|null $metric The RelatedPostsMetric value. + * @param string|null $period The RelatedPostsPeriod value. * @param array $extra_data Any Extra key/value pairs to add. * @return string The generated JSON string. */ protected function generate_json( - ?string $settings_metric = null, - ?string $settings_period = null, + ?string $metric = null, + ?string $period = null, array $extra_data = array() ): string { $array = $this->default_value; - unset( $array['SettingsMetric'], $array['SettingsPeriod'] ); + unset( $array['RelatedPostsMetric'], $array['RelatedPostsPeriod'] ); - if ( null !== $settings_metric ) { - $array['SettingsMetric'] = $settings_metric; + if ( null !== $metric ) { + $array['RelatedPostsMetric'] = $metric; } - if ( null !== $settings_period ) { - $array['SettingsPeriod'] = $settings_period; + if ( null !== $period ) { + $array['RelatedPostsPeriod'] = $period; } - ksort( $array ); + ksort( $array, SORT_NATURAL | SORT_FLAG_CASE ); return $this->wp_json_encode( array_merge( $array, $extra_data ) ); } diff --git a/tests/Integration/Endpoints/ReferrersPostDetailProxyEndpointTest.php b/tests/Integration/Endpoints/ReferrersPostDetailProxyEndpointTest.php index 03fd94c80..eac7f0429 100644 --- a/tests/Integration/Endpoints/ReferrersPostDetailProxyEndpointTest.php +++ b/tests/Integration/Endpoints/ReferrersPostDetailProxyEndpointTest.php @@ -75,7 +75,7 @@ public function test_verify_that_route_is_not_registered_when_proxy_is_disabled( * @uses \Parsely\Endpoints\Base_API_Proxy::get_data * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::__construct - * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::permission_callback + * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::run * @uses \Parsely\Parsely::site_id_is_missing * @uses \Parsely\Parsely::site_id_is_set @@ -96,7 +96,7 @@ public function test_get_items_fails_when_site_id_is_not_set(): void { * @uses \Parsely\Endpoints\Base_API_Proxy::get_data * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::__construct - * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::permission_callback + * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::run * @uses \Parsely\Parsely::site_id_is_missing * @uses \Parsely\Parsely::site_id_is_set @@ -112,9 +112,9 @@ public function test_get_items_fails_when_api_secret_is_not_set(): void { /** * Verifies default user capability filter. * - * @covers \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::permission_callback + * @covers \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call + * @uses \Parsely\RemoteAPI\Referrers_Post_Detail_API::is_available_to_current_user */ public function test_user_is_allowed_to_make_proxy_api_call_if_default_user_capability_is_changed(): void { parent::run_test_user_is_allowed_to_make_proxy_api_call_if_default_user_capability_is_changed(); @@ -123,9 +123,9 @@ public function test_user_is_allowed_to_make_proxy_api_call_if_default_user_capa /** * Verifies endpoint specific user capability filter. * - * @covers \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::permission_callback + * @covers \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call + * @uses \Parsely\RemoteAPI\Referrers_Post_Detail_API::is_available_to_current_user */ public function test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific_user_capability_is_changed(): void { parent::run_test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific_user_capability_is_changed(); @@ -140,7 +140,7 @@ public function test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::__construct * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::generate_data - * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::permission_callback + * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Referrers_Post_Detail_API_Proxy::run * @uses \Parsely\Parsely::site_id_is_missing * @uses \Parsely\Parsely::site_id_is_set diff --git a/tests/Integration/Endpoints/RelatedProxyEndpointTest.php b/tests/Integration/Endpoints/RelatedProxyEndpointTest.php index e12f954f0..d576f3bc7 100644 --- a/tests/Integration/Endpoints/RelatedProxyEndpointTest.php +++ b/tests/Integration/Endpoints/RelatedProxyEndpointTest.php @@ -74,7 +74,7 @@ public function test_verify_that_route_is_not_registered_when_proxy_is_disabled( * @uses \Parsely\Endpoints\Base_API_Proxy::get_data * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint * @uses \Parsely\Endpoints\Related_API_Proxy::__construct - * @uses \Parsely\Endpoints\Related_API_Proxy::permission_callback + * @uses \Parsely\Endpoints\Related_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Related_API_Proxy::run * @uses \Parsely\Parsely::site_id_is_missing * @uses \Parsely\Parsely::site_id_is_set @@ -94,7 +94,7 @@ public function test_get_items_fails_when_site_id_is_not_set(): void { * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint * @uses \Parsely\Endpoints\Related_API_Proxy::__construct * @uses \Parsely\Endpoints\Related_API_Proxy::generate_data - * @uses \Parsely\Endpoints\Related_API_Proxy::permission_callback + * @uses \Parsely\Endpoints\Related_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Related_API_Proxy::run * @uses \Parsely\Parsely::site_id_is_missing * @uses \Parsely\Parsely::site_id_is_set diff --git a/tests/Integration/Endpoints/StatsPostDetailProxyEndpointTest.php b/tests/Integration/Endpoints/StatsPostDetailProxyEndpointTest.php index dd074eb47..67280bac2 100644 --- a/tests/Integration/Endpoints/StatsPostDetailProxyEndpointTest.php +++ b/tests/Integration/Endpoints/StatsPostDetailProxyEndpointTest.php @@ -75,7 +75,7 @@ public function test_verify_that_route_is_not_registered_when_proxy_is_disabled( * @uses \Parsely\Endpoints\Base_API_Proxy::get_data * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::__construct - * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::permission_callback + * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::run * @uses \Parsely\Parsely::site_id_is_missing * @uses \Parsely\Parsely::site_id_is_set @@ -96,7 +96,7 @@ public function test_get_items_fails_when_site_id_is_not_set(): void { * @uses \Parsely\Endpoints\Base_API_Proxy::get_data * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::__construct - * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::permission_callback + * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::run * @uses \Parsely\Parsely::site_id_is_missing * @uses \Parsely\Parsely::site_id_is_set @@ -112,9 +112,8 @@ public function test_get_items_fails_when_api_secret_is_not_set(): void { /** * Verifies default user capability filter. * - * @covers \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::permission_callback + * @covers \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call */ public function test_user_is_allowed_to_make_proxy_api_call_if_default_user_capability_is_changed(): void { parent::run_test_user_is_allowed_to_make_proxy_api_call_if_default_user_capability_is_changed(); @@ -123,9 +122,8 @@ public function test_user_is_allowed_to_make_proxy_api_call_if_default_user_capa /** * Verifies endpoint specific user capability filter. * - * @covers \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::permission_callback + * @covers \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Base_Endpoint::__construct - * @uses \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call */ public function test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific_user_capability_is_changed(): void { parent::run_test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific_user_capability_is_changed( 'analytics_post_detail' ); @@ -140,7 +138,7 @@ public function test_user_is_allowed_to_make_proxy_api_call_if_endpoint_specific * @uses \Parsely\Endpoints\Base_API_Proxy::register_endpoint * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::__construct * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::generate_data - * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::permission_callback + * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::is_available_to_current_user * @uses \Parsely\Endpoints\Analytics_Post_Detail_API_Proxy::run * @uses \Parsely\Parsely::site_id_is_missing * @uses \Parsely\Parsely::site_id_is_set @@ -197,6 +195,7 @@ function () use ( &$dispatched ): array { 'url' => 'https://example.com', 'views' => '2,158', 'visitors' => '1,537', + 'rawUrl' => 'https://example.com', ), ), ), diff --git a/tests/Integration/Metadata/NonPostTestCase.php b/tests/Integration/Metadata/NonPostTestCase.php index 5ff79640e..44d087016 100644 --- a/tests/Integration/Metadata/NonPostTestCase.php +++ b/tests/Integration/Metadata/NonPostTestCase.php @@ -34,8 +34,12 @@ public function assert_data_has_required_properties( array $structured_data ): v array_walk( $required_properties, - static function ( $property ) use ( $structured_data ) { - self::assertArrayHasKey( $property, $structured_data, 'Data does not have required property: ' . $property ); + static function ( string $property ) use ( $structured_data ) { + self::assertArrayHasKey( + $property, + $structured_data, + 'Data does not have required property: ' . $property + ); } ); } @@ -45,7 +49,7 @@ static function ( $property ) use ( $structured_data ) { * * @return array */ - private function get_required_properties(): array { + protected function get_required_properties(): array { return array( '@context', '@type', diff --git a/tests/Integration/Metadata/SinglePageTest.php b/tests/Integration/Metadata/SinglePageTest.php index 01290aacc..7afc84600 100644 --- a/tests/Integration/Metadata/SinglePageTest.php +++ b/tests/Integration/Metadata/SinglePageTest.php @@ -20,14 +20,24 @@ * @covers \Parsely\Metadata::construct_metadata */ final class SinglePageTest extends NonPostTestCase { - /** * Verifies that the metadata generated for Single Page pages is as - * expected. + * expected. No author, category, or tag is set. + * + * @since 3.14.0 * - * @covers \Parsely\Metadata::__construct - * @covers \Parsely\Metadata::construct_metadata + * @covers \Parsely\Metadata\Metadata_Builder::build_article_section + * @covers \Parsely\Metadata\Metadata_Builder::build_author + * @covers \Parsely\Metadata\Metadata_Builder::build_image + * @covers \Parsely\Metadata\Metadata_Builder::build_keywords + * @covers \Parsely\Metadata\Metadata_Builder::build_metadata_post_times + * @covers \Parsely\Metadata\Metadata_Builder::build_thumbnail_url + * @covers \Parsely\Metadata\Metadata_Builder::get_author_names + * @covers \Parsely\Metadata\Metadata_Builder::get_category_name + * @covers \Parsely\Metadata\Metadata_Builder::get_coauthor_names * @covers \Parsely\Metadata\Metadata_Builder::get_current_url + * @covers \Parsely\Metadata\Metadata_Builder::get_tags + * @uses \Parsely\Metadata::__construct * @uses \Parsely\Metadata\Metadata_Builder::__construct * @uses \Parsely\Metadata\Metadata_Builder::build_basic * @uses \Parsely\Metadata\Metadata_Builder::clean_value @@ -35,25 +45,404 @@ final class SinglePageTest extends NonPostTestCase { * @uses \Parsely\Metadata\Page_Builder::build_headline * @uses \Parsely\Metadata\Page_Builder::build_url * @uses \Parsely\Metadata\Page_Builder::get_metadata + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials * @uses \Parsely\Parsely::get_options * @uses \Parsely\Parsely::post_has_trackable_status + * @uses \Parsely\Parsely::set_managed_options * @uses \Parsely\Parsely::update_metadata_endpoint + * @uses \Parsely\Utils\get_default_category * @group metadata */ public function test_single_page(): void { + $this->run_single_page_test( false, false, false ); + } + + /** + * Verifies that the metadata generated for Single Page pages is as + * expected. An author is set. + * + * @since 3.14.0 + * + * @covers \Parsely\Metadata\Metadata_Builder::build_article_section + * @covers \Parsely\Metadata\Metadata_Builder::build_author + * @covers \Parsely\Metadata\Metadata_Builder::build_image + * @covers \Parsely\Metadata\Metadata_Builder::build_keywords + * @covers \Parsely\Metadata\Metadata_Builder::build_metadata_post_times + * @covers \Parsely\Metadata\Metadata_Builder::build_thumbnail_url + * @covers \Parsely\Metadata\Metadata_Builder::get_author_names + * @covers \Parsely\Metadata\Metadata_Builder::get_category_name + * @covers \Parsely\Metadata\Metadata_Builder::get_coauthor_names + * @covers \Parsely\Metadata\Metadata_Builder::get_current_url + * @covers \Parsely\Metadata\Metadata_Builder::get_tags + * @uses \Parsely\Metadata::__construct + * @uses \Parsely\Metadata\Metadata_Builder::__construct + * @uses \Parsely\Metadata\Metadata_Builder::build_basic + * @uses \Parsely\Metadata\Metadata_Builder::clean_value + * @uses \Parsely\Metadata\Page_Builder::__construct + * @uses \Parsely\Metadata\Page_Builder::build_headline + * @uses \Parsely\Metadata\Page_Builder::build_url + * @uses \Parsely\Metadata\Page_Builder::get_metadata + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::post_has_trackable_status + * @uses \Parsely\Parsely::set_managed_options + * @uses \Parsely\Parsely::update_metadata_endpoint + * @uses \Parsely\Utils\get_default_category + * @group metadata + */ + public function test_single_page_with_author(): void { + $this->run_single_page_test( true, false, false ); + } + + /** + * Verifies that the metadata generated for Single Page pages is as + * expected. A category is set. + * + * @since 3.14.0 + * + * @covers \Parsely\Metadata\Metadata_Builder::build_article_section + * @covers \Parsely\Metadata\Metadata_Builder::build_author + * @covers \Parsely\Metadata\Metadata_Builder::build_image + * @covers \Parsely\Metadata\Metadata_Builder::build_keywords + * @covers \Parsely\Metadata\Metadata_Builder::build_metadata_post_times + * @covers \Parsely\Metadata\Metadata_Builder::build_thumbnail_url + * @covers \Parsely\Metadata\Metadata_Builder::get_author_names + * @covers \Parsely\Metadata\Metadata_Builder::get_category_name + * @covers \Parsely\Metadata\Metadata_Builder::get_coauthor_names + * @covers \Parsely\Metadata\Metadata_Builder::get_current_url + * @covers \Parsely\Metadata\Metadata_Builder::get_tags + * @uses \Parsely\Metadata::__construct + * @uses \Parsely\Metadata\Metadata_Builder::__construct + * @uses \Parsely\Metadata\Metadata_Builder::build_basic + * @uses \Parsely\Metadata\Metadata_Builder::clean_value + * @uses \Parsely\Metadata\Page_Builder::__construct + * @uses \Parsely\Metadata\Page_Builder::build_headline + * @uses \Parsely\Metadata\Page_Builder::build_url + * @uses \Parsely\Metadata\Page_Builder::get_metadata + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::post_has_trackable_status + * @uses \Parsely\Parsely::set_managed_options + * @uses \Parsely\Parsely::update_metadata_endpoint + * @uses \Parsely\Utils\get_default_category + * @group metadata + */ + public function test_single_page_with_category(): void { + $this->run_single_page_test( false, true, false ); + } + + /** + * Verifies that the metadata generated for Single Page pages is as + * expected. A tag is set. + * + * @since 3.14.0 + * + * @covers \Parsely\Metadata\Metadata_Builder::build_article_section + * @covers \Parsely\Metadata\Metadata_Builder::build_author + * @covers \Parsely\Metadata\Metadata_Builder::build_image + * @covers \Parsely\Metadata\Metadata_Builder::build_keywords + * @covers \Parsely\Metadata\Metadata_Builder::build_metadata_post_times + * @covers \Parsely\Metadata\Metadata_Builder::build_thumbnail_url + * @covers \Parsely\Metadata\Metadata_Builder::get_author_names + * @covers \Parsely\Metadata\Metadata_Builder::get_category_name + * @covers \Parsely\Metadata\Metadata_Builder::get_coauthor_names + * @covers \Parsely\Metadata\Metadata_Builder::get_current_url + * @covers \Parsely\Metadata\Metadata_Builder::get_tags + * @uses \Parsely\Metadata::__construct + * @uses \Parsely\Metadata\Metadata_Builder::__construct + * @uses \Parsely\Metadata\Metadata_Builder::build_basic + * @uses \Parsely\Metadata\Metadata_Builder::clean_value + * @uses \Parsely\Metadata\Page_Builder::__construct + * @uses \Parsely\Metadata\Page_Builder::build_headline + * @uses \Parsely\Metadata\Page_Builder::build_url + * @uses \Parsely\Metadata\Page_Builder::get_metadata + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::post_has_trackable_status + * @uses \Parsely\Parsely::set_managed_options + * @uses \Parsely\Parsely::update_metadata_endpoint + * @uses \Parsely\Utils\get_default_category + * @group metadata + */ + public function test_single_page_with_tag(): void { + $this->run_single_page_test( false, false, true ); + } + + /** + * Verifies that the metadata generated for Single Page pages is as + * expected. A tag is set and the lowercase_tags option is off. + * + * @since 3.14.0 + * + * @covers \Parsely\Metadata\Metadata_Builder::build_article_section + * @covers \Parsely\Metadata\Metadata_Builder::build_author + * @covers \Parsely\Metadata\Metadata_Builder::build_image + * @covers \Parsely\Metadata\Metadata_Builder::build_keywords + * @covers \Parsely\Metadata\Metadata_Builder::build_metadata_post_times + * @covers \Parsely\Metadata\Metadata_Builder::build_thumbnail_url + * @covers \Parsely\Metadata\Metadata_Builder::get_author_names + * @covers \Parsely\Metadata\Metadata_Builder::get_category_name + * @covers \Parsely\Metadata\Metadata_Builder::get_coauthor_names + * @covers \Parsely\Metadata\Metadata_Builder::get_current_url + * @covers \Parsely\Metadata\Metadata_Builder::get_tags + * @uses \Parsely\Metadata::__construct + * @uses \Parsely\Metadata\Metadata_Builder::__construct + * @uses \Parsely\Metadata\Metadata_Builder::build_basic + * @uses \Parsely\Metadata\Metadata_Builder::clean_value + * @uses \Parsely\Metadata\Page_Builder::__construct + * @uses \Parsely\Metadata\Page_Builder::build_headline + * @uses \Parsely\Metadata\Page_Builder::build_url + * @uses \Parsely\Metadata\Page_Builder::get_metadata + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::post_has_trackable_status + * @uses \Parsely\Parsely::set_managed_options + * @uses \Parsely\Parsely::update_metadata_endpoint + * @uses \Parsely\Utils\get_default_category + * @group metadata + */ + public function test_single_page_with_tag_lowercase_off(): void { + $this->run_single_page_test( false, false, true, false ); + } + + /** + * Verifies that the metadata generated for Single Page pages is as + * expected. An author and a category are set. + * + * @since 3.14.0 + * + * @covers \Parsely\Metadata\Metadata_Builder::build_article_section + * @covers \Parsely\Metadata\Metadata_Builder::build_author + * @covers \Parsely\Metadata\Metadata_Builder::build_image + * @covers \Parsely\Metadata\Metadata_Builder::build_keywords + * @covers \Parsely\Metadata\Metadata_Builder::build_metadata_post_times + * @covers \Parsely\Metadata\Metadata_Builder::build_thumbnail_url + * @covers \Parsely\Metadata\Metadata_Builder::get_author_names + * @covers \Parsely\Metadata\Metadata_Builder::get_category_name + * @covers \Parsely\Metadata\Metadata_Builder::get_coauthor_names + * @covers \Parsely\Metadata\Metadata_Builder::get_current_url + * @covers \Parsely\Metadata\Metadata_Builder::get_tags + * @uses \Parsely\Metadata::__construct + * @uses \Parsely\Metadata\Metadata_Builder::__construct + * @uses \Parsely\Metadata\Metadata_Builder::build_basic + * @uses \Parsely\Metadata\Metadata_Builder::clean_value + * @uses \Parsely\Metadata\Page_Builder::__construct + * @uses \Parsely\Metadata\Page_Builder::build_headline + * @uses \Parsely\Metadata\Page_Builder::build_url + * @uses \Parsely\Metadata\Page_Builder::get_metadata + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::post_has_trackable_status + * @uses \Parsely\Parsely::set_managed_options + * @uses \Parsely\Parsely::update_metadata_endpoint + * @uses \Parsely\Utils\get_default_category + * @group metadata + */ + public function test_single_page_with_author_and_category(): void { + $this->run_single_page_test( true, true, false ); + } + + /** + * Verifies that the metadata generated for Single Page pages is as + * expected. An author and a tag are set. + * + * @since 3.14.0 + * + * @covers \Parsely\Metadata\Metadata_Builder::build_article_section + * @covers \Parsely\Metadata\Metadata_Builder::build_author + * @covers \Parsely\Metadata\Metadata_Builder::build_image + * @covers \Parsely\Metadata\Metadata_Builder::build_keywords + * @covers \Parsely\Metadata\Metadata_Builder::build_metadata_post_times + * @covers \Parsely\Metadata\Metadata_Builder::build_thumbnail_url + * @covers \Parsely\Metadata\Metadata_Builder::get_author_names + * @covers \Parsely\Metadata\Metadata_Builder::get_category_name + * @covers \Parsely\Metadata\Metadata_Builder::get_coauthor_names + * @covers \Parsely\Metadata\Metadata_Builder::get_current_url + * @covers \Parsely\Metadata\Metadata_Builder::get_tags + * @uses \Parsely\Metadata::__construct + * @uses \Parsely\Metadata\Metadata_Builder::__construct + * @uses \Parsely\Metadata\Metadata_Builder::build_basic + * @uses \Parsely\Metadata\Metadata_Builder::clean_value + * @uses \Parsely\Metadata\Page_Builder::__construct + * @uses \Parsely\Metadata\Page_Builder::build_headline + * @uses \Parsely\Metadata\Page_Builder::build_url + * @uses \Parsely\Metadata\Page_Builder::get_metadata + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::post_has_trackable_status + * @uses \Parsely\Parsely::set_managed_options + * @uses \Parsely\Parsely::update_metadata_endpoint + * @uses \Parsely\Utils\get_default_category + * @group metadata + */ + public function test_single_page_with_author_and_tag(): void { + $this->run_single_page_test( true, false, true ); + } + + /** + * Verifies that the metadata generated for Single Page pages is as + * expected. A category and a tag are set. + * + * @since 3.14.0 + * + * @covers \Parsely\Metadata\Metadata_Builder::build_article_section + * @covers \Parsely\Metadata\Metadata_Builder::build_author + * @covers \Parsely\Metadata\Metadata_Builder::build_image + * @covers \Parsely\Metadata\Metadata_Builder::build_keywords + * @covers \Parsely\Metadata\Metadata_Builder::build_metadata_post_times + * @covers \Parsely\Metadata\Metadata_Builder::build_thumbnail_url + * @covers \Parsely\Metadata\Metadata_Builder::get_author_names + * @covers \Parsely\Metadata\Metadata_Builder::get_category_name + * @covers \Parsely\Metadata\Metadata_Builder::get_coauthor_names + * @covers \Parsely\Metadata\Metadata_Builder::get_current_url + * @covers \Parsely\Metadata\Metadata_Builder::get_tags + * @uses \Parsely\Metadata::__construct + * @uses \Parsely\Metadata\Metadata_Builder::__construct + * @uses \Parsely\Metadata\Metadata_Builder::build_basic + * @uses \Parsely\Metadata\Metadata_Builder::clean_value + * @uses \Parsely\Metadata\Page_Builder::__construct + * @uses \Parsely\Metadata\Page_Builder::build_headline + * @uses \Parsely\Metadata\Page_Builder::build_url + * @uses \Parsely\Metadata\Page_Builder::get_metadata + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::post_has_trackable_status + * @uses \Parsely\Parsely::set_managed_options + * @uses \Parsely\Parsely::update_metadata_endpoint + * @uses \Parsely\Utils\get_default_category + * @group metadata + */ + public function test_single_page_with_category_and_tag(): void { + $this->run_single_page_test( false, true, true ); + } + + /** + * Verifies that the metadata generated for Single Page pages is as + * expected. An author, a category, and a tag are set. + * + * @since 3.14.0 + * + * @covers \Parsely\Metadata\Metadata_Builder::build_article_section + * @covers \Parsely\Metadata\Metadata_Builder::build_author + * @covers \Parsely\Metadata\Metadata_Builder::build_image + * @covers \Parsely\Metadata\Metadata_Builder::build_keywords + * @covers \Parsely\Metadata\Metadata_Builder::build_metadata_post_times + * @covers \Parsely\Metadata\Metadata_Builder::build_thumbnail_url + * @covers \Parsely\Metadata\Metadata_Builder::get_author_names + * @covers \Parsely\Metadata\Metadata_Builder::get_category_name + * @covers \Parsely\Metadata\Metadata_Builder::get_coauthor_names + * @covers \Parsely\Metadata\Metadata_Builder::get_current_url + * @covers \Parsely\Metadata\Metadata_Builder::get_tags + * @uses \Parsely\Metadata::__construct + * @uses \Parsely\Metadata\Metadata_Builder::__construct + * @uses \Parsely\Metadata\Metadata_Builder::build_basic + * @uses \Parsely\Metadata\Metadata_Builder::clean_value + * @uses \Parsely\Metadata\Page_Builder::__construct + * @uses \Parsely\Metadata\Page_Builder::build_headline + * @uses \Parsely\Metadata\Page_Builder::build_url + * @uses \Parsely\Metadata\Page_Builder::get_metadata + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::post_has_trackable_status + * @uses \Parsely\Parsely::set_managed_options + * @uses \Parsely\Parsely::update_metadata_endpoint + * @uses \Parsely\Utils\get_default_category + * @group metadata + */ + public function test_single_page_with_author_and_category_and_tag(): void { + $this->run_single_page_test( true, true, true ); + } + + /** + * Runs the single page test with the desired parameters. + * + * @since 3.14.0 Renamed from test_single_page. + * + * @param bool $with_author Whether to test with an author. + * @param bool $with_category Whether to test with a category. + * @param bool $with_tag Whether to test with a tag. + * @param bool $lowercase_tags The value of the `lowercase_tags` option. + */ + private function run_single_page_test( + bool $with_author, + bool $with_category, + bool $with_tag, + bool $lowercase_tags = true + ): void { // Setup Parsely object. $parsely = new Parsely(); + $page_data = array( + 'post_type' => 'page', + 'post_title' => 'Single Page', + 'post_name' => 'foo', + 'post_date' => '2024-01-01 12:00:00', + ); + + if ( $with_author ) { + $page_data['post_author'] = 1; + } + // Insert a single page. /** @var int $page_id */ - $page_id = self::factory()->post->create( - array( - 'post_type' => 'page', - 'post_title' => 'Single Page', - 'post_name' => 'foo', - ) - ); - $page = $this->get_post( $page_id ); + $page_id = self::factory()->post->create( $page_data ); + + if ( $with_category ) { + // Create a category and assign it to the page. + /** @var int $category_id */ + $category_id = self::factory()->category->create( + array( + 'name' => 'Category 1', + 'slug' => 'category-1', + ) + ); + wp_set_post_categories( $page_id, array( $category_id ) ); + } + + if ( $with_tag ) { + // Set the `lowercase_tags` option as needed. + $parsely_options = $parsely->get_options(); + $parsely_options['lowercase_tags'] = $lowercase_tags; + update_option( 'parsely', $parsely_options ); + + // Create a tag and assign it to the page. + $tag_id = self::factory()->tag->create( + array( + 'name' => 'Tag 1', + 'slug' => 'tag-1', + ) + ); + wp_set_post_tags( $page_id, array( $tag_id ) ); + } + + $page = $this->get_post( $page_id ); // Set permalinks, as Parsely currently strips ?page_id=... from the URL // property. See https://github.com/Parsely/wp-parsely/issues/151. @@ -74,9 +463,76 @@ public function test_single_page(): void { // The headline should be the post_title of the page. self::assertSame( 'Single Page', $structured_data['headline'] ?? null ); self::assertSame( get_permalink( $page_id ), $structured_data['url'] ?? null ); + self::assertSame( '2024-01-01T12:00:00Z', $structured_data['dateCreated'] ); + self::assertSame( '2024-01-01T12:00:00Z', $structured_data['datePublished'] ); + self::assertSame( '2024-01-01T12:00:00Z', $structured_data['dateModified'] ); + self::assertSame( '', $structured_data['thumbnailUrl'] ); + self::assertSame( + array( + '@type' => 'ImageObject', + 'url' => '', + ), + $structured_data['image'] + ); self::assertQueryTrue( 'is_page', 'is_singular' ); + // Test author/creator values. + if ( $with_author ) { + /** @var array> $author */ + $author = $structured_data['author']; + /** @var array $creator */ + $creator = $structured_data['creator']; + + self::assertSame( 'Person', $author[0]['@type'] ); + self::assertSame( 'admin', $author[0]['name'] ); + self::assertSame( 'admin', $creator[0] ); + } else { + self::assertSame( array(), $structured_data['author'] ); + self::assertSame( array(), $structured_data['creator'] ); + } + + // Test category value. + if ( $with_category ) { + self::assertSame( 'Category 1', $structured_data['articleSection'] ); + } else { + self::assertSame( 'Uncategorized', $structured_data['articleSection'] ); + } + + // Test tag value. + if ( $with_tag ) { + /** @var array $keywords */ + $keywords = $structured_data['keywords']; + + if ( true === $lowercase_tags ) { + self::assertSame( 'tag 1', $keywords[0] ); + } else { + self::assertSame( 'Tag 1', $keywords[0] ); + } + } else { + self::assertSame( array(), $structured_data['keywords'] ); + } + // Reset permalinks to plain. $wp_rewrite->set_permalink_structure( '' ); } + + /** + * Returns the required properties for non-posts. + * + * @since 3.14.0 + * + * @return array + */ + protected function get_required_properties(): array { + return array( + '@context', + '@type', + 'articleSection', + 'author', + 'creator', + 'headline', + 'keywords', + 'url', + ); + } } diff --git a/tests/Integration/OptionsTest.php b/tests/Integration/OptionsTest.php index b653247e9..a454b122b 100644 --- a/tests/Integration/OptionsTest.php +++ b/tests/Integration/OptionsTest.php @@ -46,9 +46,15 @@ public function tear_down(): void { * * @since 3.0.0 * + * @covers \Parsely\Parsely::get_default_options * @covers \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::set_default_full_metadata_in_non_posts * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::set_managed_options */ public function test_default_options_are_returned_when_options_are_corrupted_or_not_set(): void { add_option( Parsely::OPTIONS_KEY, 'someinvalidvalue' ); @@ -66,8 +72,13 @@ public function test_default_options_are_returned_when_options_are_corrupted_or_ * @since 3.9.0 * * @covers \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::set_default_full_metadata_in_non_posts * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::set_managed_options */ public function test_get_options_returns_correct_track_as_defaults(): void { $options = self::$parsely->get_options(); @@ -82,7 +93,13 @@ public function test_get_options_returns_correct_track_as_defaults(): void { * @since 3.9.0 * * @covers \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::set_default_full_metadata_in_non_posts + * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::set_managed_options */ public function test_set_default_track_as_values_should_not_be_called_when_saved_options_exist(): void { $options = self::$parsely->get_options(); @@ -103,8 +120,13 @@ public function test_set_default_track_as_values_should_not_be_called_when_saved * @since 3.9.0 * * @covers \Parsely\Parsely::get_options + * @covers \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed * @uses \Parsely\Parsely::get_managed_credentials - * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::set_default_full_metadata_in_non_posts + * @uses \Parsely\Parsely::set_managed_options */ public function test_get_options_track_as_defaults_when_cpts_are_registered(): void { $custom_post_types = array( @@ -169,6 +191,202 @@ public function test_get_options_track_as_defaults_when_cpts_are_registered(): v self::assertSame( array( 'page', 'custom_page_type' ), $options['track_page_types'] ); } + /** + * Verifies that get_options() returns the correct full_metadata_in_non_posts + * option default value. + * + * @since 3.14.0 + * + * @covers \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::set_default_full_metadata_in_non_posts + * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::set_managed_options + */ + public function test_get_options_returns_correct_full_metadata_in_non_posts_default_value(): void { + $options = self::$parsely->get_options(); + self::assertSame( true, $options['full_metadata_in_non_posts'] ); + } + + /** + * Verifies that the set_default_full_metadata_in_non_posts() function + * doesn't get called when saved plugin options exist. + * + * @since 3.14.0 + * + * @covers \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::set_default_full_metadata_in_non_posts + * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::set_managed_options + */ + public function test_set_default_full_metadata_in_non_posts_values_should_not_be_called_when_saved_options_exist(): void { + $option_key = 'full_metadata_in_non_posts'; + $options = self::$parsely->get_options(); + $options[ $option_key ] = false; + + add_option( Parsely::OPTIONS_KEY, $options ); + $saved_options = self::$parsely->get_options(); + + self::assertSame( false, $saved_options[ $option_key ] ); + } + + /** + * Verifies that the set_default_full_metadata_in_non_posts() function + * returns the expected value under different circumstances: + * + * - Option is saved (get value from database). + * - Option isn't saved (get value from defaults). + * - Option value is influenced by a metadata filter (modify default value). + * + * @since 3.14.0 + * + * @covers \Parsely\Parsely::get_options + * @covers \Parsely\Parsely::set_default_full_metadata_in_non_posts + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_default_options + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::sanitize_managed_option + * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::set_managed_options + */ + public function test_set_default_full_metadata_in_non_posts_returns_expected_values(): void { + $option_key = 'full_metadata_in_non_posts'; + $default_options = self::$parsely->get_default_options(); + self::assertSame( true, $default_options[ $option_key ] ); + + // Add a filter that will make the default value to return false. + add_filter( 'wp_parsely_metadata', '__return_true' ); + + // No options are saved. Should get the value from option defaults. + self::assertSame( + false, + ( new Parsely() )->get_options()[ $option_key ] + ); + + // Options are saved. Should get the value from the saved option. + add_option( Parsely::OPTIONS_KEY, $default_options ); + self::assertSame( + true, + ( new Parsely() )->get_options()[ $option_key ] + ); + } + + /** + * Verifies that the set_default_full_metadata_in_non_posts() function + * returns false when different metadata filters are being used. + * + * @since 3.14.0 + * + * @covers \Parsely\Parsely::get_options + * @covers \Parsely\Parsely::set_default_full_metadata_in_non_posts + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::set_managed_options + */ + public function test_set_default_full_metadata_in_non_posts_returns_false_when_metadata_filters_are_used(): void { + $option_key = 'full_metadata_in_non_posts'; + + // Filter tags and how they should influence the expected value of the + // full_metadata_in_non_posts option. + $filters = array( + 'wp_parsely_metadata' => false, + 'imaginary_filter' => true, // Fake filter tag that should have no effect. + 'wp_parsely_post_tags' => false, + 'wp_parsely_load_js_tracker' => true, // Real filter tag that should have no effect. + 'wp_parsely_permalink' => false, + 'wp_parsely_enable_admin_bar' => true, // Real filter tag that should have no effect. + 'wp_parsely_post_category' => false, + 'wp_parsely_pre_authors' => false, + 'wp_parsely_post_authors' => false, + 'wp_parsely_custom_taxonomies' => false, + 'wp_parsely_post_type' => false, + ); + + /** + * Fake callback to be used when adding filters. + */ + $callback_function = function (): bool { + return true; + }; + + foreach ( $filters as $filter_name => $expected_value ) { + // Filter is not in use yet, so the result should always be true. + self::assertSame( + true, + ( new Parsely() )->get_options()[ $option_key ] + ); + + // Filter is being used now, so the result should be equal to the + // expected value. + add_filter( $filter_name, $callback_function ); + self::assertSame( + $expected_value, + ( new Parsely() )->get_options()[ $option_key ] + ); + + // Remove the last used filter for the next round. + remove_filter( $filter_name, $callback_function ); + } + } + + /** + * Verifies that the full_metadata_in_non_posts option gets set to true when + * it does not exist in the options array. + * + * @since 3.14.0 + * + * @covers \Parsely\Parsely::get_options + * @covers \Parsely\Parsely::set_default_full_metadata_in_non_posts + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::set_managed_options + */ + public function test_full_metadata_in_non_posts_gets_set_to_true_when_it_does_not_exist_in_options_array(): void { + $option_key = 'full_metadata_in_non_posts'; + $options = self::$parsely->get_options(); + unset( $options[ $option_key ] ); + self::assertSame( true, self::$parsely->get_options()[ $option_key ] ); + } + + /** + * Verifies that the full_metadata_in_non_posts option gets set to false + * when it does not exist in the options array and a metadata filter is in + * use. + * + * @since 3.14.0 + * + * @covers \Parsely\Parsely::get_options + * @covers \Parsely\Parsely::set_default_full_metadata_in_non_posts + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::set_managed_options + */ + public function test_full_metadata_in_non_posts_gets_set_to_false_when_it_does_not_exist_in_options_array_and_a_metadata_filter_is_used(): void { + $option_key = 'full_metadata_in_non_posts'; + $options = self::$parsely->get_options(); + unset( $options[ $option_key ] ); + add_filter( 'wp_parsely_metadata', '__return_true' ); + self::assertSame( false, self::$parsely->get_options()[ $option_key ] ); + } + /** * Verifies that managed options with a value different than null override * default and saved options. @@ -178,11 +396,14 @@ public function test_get_options_track_as_defaults_when_cpts_are_registered(): v * @covers \Parsely\Parsely::get_options * @covers \Parsely\Parsely::set_managed_options * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests * @uses \Parsely\Parsely::are_credentials_managed * @uses \Parsely\Parsely::get_default_options * @uses \Parsely\Parsely::get_managed_credentials * @uses \Parsely\Parsely::sanitize_managed_option + * @uses \Parsely\Parsely::set_default_full_metadata_in_non_posts * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\UI\Settings_Page::get_section_taxonomies */ public function test_set_managed_options_override_all_other_option_types(): void { $default_options = self::$parsely->get_default_options(); @@ -218,10 +439,12 @@ function () { * @covers \Parsely\Parsely::get_options * @covers \Parsely\Parsely::set_managed_options * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests * @uses \Parsely\Parsely::are_credentials_managed * @uses \Parsely\Parsely::get_default_options * @uses \Parsely\Parsely::get_managed_credentials * @uses \Parsely\Parsely::sanitize_managed_option + * @uses \Parsely\Parsely::set_default_full_metadata_in_non_posts * @uses \Parsely\Parsely::set_default_track_as_values */ public function test_null_managed_options_get_their_value_from_the_database_or_defaults(): void { @@ -259,10 +482,12 @@ function () { * @covers \Parsely\Parsely::get_options * @covers \Parsely\Parsely::set_managed_options * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests * @uses \Parsely\Parsely::are_credentials_managed * @uses \Parsely\Parsely::get_default_options * @uses \Parsely\Parsely::get_managed_credentials * @uses \Parsely\Parsely::sanitize_managed_option + * @uses \Parsely\Parsely::set_default_full_metadata_in_non_posts * @uses \Parsely\Parsely::set_default_track_as_values */ public function test_certain_options_cannot_be_set_as_managed(): void { @@ -300,8 +525,10 @@ function () { * @covers \Parsely\Parsely::sanitize_managed_option * @covers \Parsely\Parsely::set_managed_options * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests * @uses \Parsely\Parsely::are_credentials_managed * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::set_default_full_metadata_in_non_posts * @uses \Parsely\Parsely::set_default_track_as_values * @uses \Parsely\UI\Settings_Page::get_section_taxonomies * diff --git a/tests/Integration/ProxyEndpointTest.php b/tests/Integration/ProxyEndpointTest.php index 6d9ccd91c..81a923b67 100644 --- a/tests/Integration/ProxyEndpointTest.php +++ b/tests/Integration/ProxyEndpointTest.php @@ -218,7 +218,7 @@ function () { } ); - self::assertTrue( static::get_endpoint()->permission_callback() ); + self::assertTrue( static::get_endpoint()->is_available_to_current_user() ); } /** @@ -239,6 +239,6 @@ function () { } ); - self::assertTrue( static::get_endpoint()->permission_callback() ); + self::assertTrue( static::get_endpoint()->is_available_to_current_user() ); } } diff --git a/tests/Integration/RemoteAPI/AnalyticsPostsRemoteAPITest.php b/tests/Integration/RemoteAPI/AnalyticsPostsRemoteAPITest.php index e2bd9d989..c3f75b3e3 100644 --- a/tests/Integration/RemoteAPI/AnalyticsPostsRemoteAPITest.php +++ b/tests/Integration/RemoteAPI/AnalyticsPostsRemoteAPITest.php @@ -43,9 +43,14 @@ public function data_api_url(): iterable { /** * Verifies default user capability filter. * - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * + * @covers \Parsely\RemoteAPI\Analytics_Posts_API::is_available_to_current_user * @uses \Parsely\Endpoints\Base_Endpoint::__construct + * @uses \Parsely\Endpoints\Base_Endpoint::apply_capability_filters + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::set_managed_options + * @uses \Parsely\Utils\convert_endpoint_to_filter_key */ public function test_user_is_allowed_to_make_api_call_if_default_user_capability_is_changed(): void { $this->login_as_contributor(); @@ -58,15 +63,20 @@ function () { $api = new Analytics_Posts_API( new Parsely() ); - self::assertTrue( $api->is_user_allowed_to_make_api_call() ); + self::assertTrue( $api->is_available_to_current_user() ); } /** * Verifies endpoint specific user capability filter. * - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * + * @covers \Parsely\RemoteAPI\Analytics_Posts_API::is_available_to_current_user * @uses \Parsely\Endpoints\Base_Endpoint::__construct + * @uses \Parsely\Endpoints\Base_Endpoint::apply_capability_filters + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::set_managed_options + * @uses \Parsely\Utils\convert_endpoint_to_filter_key */ public function test_user_is_allowed_to_make_api_call_if_endpoint_specific_user_capability_is_changed(): void { $this->login_as_contributor(); @@ -79,15 +89,20 @@ function () { $api = new Analytics_Posts_API( new Parsely() ); - self::assertTrue( $api->is_user_allowed_to_make_api_call() ); + self::assertTrue( $api->is_available_to_current_user() ); } /** * Verifies that the endpoint specific user capability filter has more priority than the default capability filter. * - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * + * @covers \Parsely\RemoteAPI\Analytics_Posts_API::is_available_to_current_user * @uses \Parsely\Endpoints\Base_Endpoint::__construct + * @uses \Parsely\Endpoints\Base_Endpoint::apply_capability_filters + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::set_managed_options + * @uses \Parsely\Utils\convert_endpoint_to_filter_key */ public function test_endpoint_specific_user_capability_filter_have_more_priority_than_default(): void { $this->login_as_contributor(); @@ -108,6 +123,6 @@ function () { $api = new Analytics_Posts_API( new Parsely() ); - self::assertTrue( $api->is_user_allowed_to_make_api_call() ); + self::assertTrue( $api->is_available_to_current_user() ); } } diff --git a/tests/Integration/RemoteAPI/RelatedRemoteAPITest.php b/tests/Integration/RemoteAPI/RelatedRemoteAPITest.php index 00382e39a..885e71f0f 100644 --- a/tests/Integration/RemoteAPI/RelatedRemoteAPITest.php +++ b/tests/Integration/RemoteAPI/RelatedRemoteAPITest.php @@ -71,14 +71,18 @@ public function data_api_url(): iterable { /** * Verifies that the endpoint does not have filters that check user capability. * - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call - * + * @covers \Parsely\RemoteAPI\Related_API::is_available_to_current_user * @uses \Parsely\Endpoints\Base_Endpoint::__construct + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::__construct + * @uses \Parsely\Parsely::allow_parsely_remote_requests + * @uses \Parsely\Parsely::are_credentials_managed + * @uses \Parsely\Parsely::set_managed_options */ public function test_related_endpoint_does_not_have_user_capability_filters(): void { $api = new Related_API( new Parsely() ); - self::assertTrue( $api->is_user_allowed_to_make_api_call() ); + self::assertTrue( $api->is_available_to_current_user() ); $this->assert_wp_hooks_availability( array( 'wp_parsely_user_capability_for_all_private_apis', diff --git a/tests/Integration/RemoteAPI/content-suggestions/BaseContentSuggestionsAPITest.php b/tests/Integration/RemoteAPI/content-suggestions/BaseContentSuggestionsAPITest.php new file mode 100644 index 000000000..0ea81d53b --- /dev/null +++ b/tests/Integration/RemoteAPI/content-suggestions/BaseContentSuggestionsAPITest.php @@ -0,0 +1,61 @@ + 'my-key', + 'api_secret' => 'my-secret', + ) + ); + + $request_options = self::$remote_api->get_request_options(); + + // Ensure that $request_options is an array and 'headers' key exists. + self::assertIsArray( $request_options ); + self::assertArrayHasKey( 'headers', $request_options ); + + $headers = $request_options['headers']; + self::assertIsArray( $headers ); // Ensures $headers is indeed an array. + + // Verify the Content-Type header is present and its value is application/json. + self::assertArrayHasKey( 'Content-Type', $headers ); + self::assertEquals( 'application/json; charset=utf-8', $headers['Content-Type'] ); + + // Verify the API key is present in the headers and its value matches the one set in the options. + self::assertArrayHasKey( 'X-APIKEY-SECRET', $headers ); + self::assertEquals( 'my-secret', $headers['X-APIKEY-SECRET'] ); + } +} diff --git a/tests/Integration/RemoteAPI/content-suggestions/SuggestMetaDescriptionAPITest.php b/tests/Integration/RemoteAPI/content-suggestions/SuggestBriefAPITest.php similarity index 57% rename from tests/Integration/RemoteAPI/content-suggestions/SuggestMetaDescriptionAPITest.php rename to tests/Integration/RemoteAPI/content-suggestions/SuggestBriefAPITest.php index 782fbde38..6c3ab62e1 100644 --- a/tests/Integration/RemoteAPI/content-suggestions/SuggestMetaDescriptionAPITest.php +++ b/tests/Integration/RemoteAPI/content-suggestions/SuggestBriefAPITest.php @@ -1,6 +1,6 @@ array( array( 'apikey' => 'my-key', - 'secret' => 'my-secret', - 'title' => 'This is a title', ), Parsely::PUBLIC_SUGGESTIONS_API_BASE_URL . - '/suggest-meta-description?apikey=my-key&secret=my-secret&title=This is a title', + '/suggest-brief?apikey=my-key', ); } /** - * Mocks a successful HTTP response to the Content Suggestion suggest-meta-description + * Mocks a successful HTTP response to the Content Suggestion suggest-brief * API endpoint. * * @since 3.13.0 @@ -72,17 +70,19 @@ public function data_api_url(): iterable { * * @phpstan-ignore-next-line */ - public function mock_successful_suggest_meta_description_response( + public function mock_successful_suggest_brief_response( string $response, array $args, string $url ) { - if ( ! str_contains( $url, 'suggest-meta-description' ) ) { + if ( ! str_contains( $url, 'suggest-brief' ) ) { return false; } $response = array( - 'meta_description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'result' => array( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + ), ); return array( @@ -104,27 +104,34 @@ public function mock_successful_suggest_meta_description_response( * * @since 3.13.0 * - * @covers \Parsely\RemoteAPI\ContentSuggestions\Suggest_Meta_Description_API::get_suggestion - * @uses \Parsely\RemoteAPI\ContentSuggestions\Suggest_Meta_Description_API::__construct - * @uses \Parsely\RemoteAPI\ContentSuggestions\Suggest_Meta_Description_API::get_suggestion - * @uses \Parsely\RemoteAPI\ContentSuggestions\Content_Suggestions_Base_API::__construct - * @uses \Parsely\RemoteAPI\ContentSuggestions\Content_Suggestions_Base_API::get_api_url - * @uses \Parsely\RemoteAPI\Remote_API_Base::__construct + * @covers \Parsely\RemoteAPI\ContentSuggestions\Suggest_Brief_API::get_suggestion + * @uses \Parsely\Parsely::api_secret_is_set() + * @uses \Parsely\Parsely::get_managed_credentials() + * @uses \Parsely\Parsely::get_options() + * @uses \Parsely\Parsely::get_site_id() + * @uses \Parsely\Parsely::set_default_track_as_values() + * @uses \Parsely\Parsely::site_id_is_set() + * @uses \Parsely\RemoteAPI\Base_Endpoint_Remote::validate_required_constraints() + * @uses \Parsely\RemoteAPI\ContentSuggestions\Content_Suggestions_Base_API::get_api_url() + * @uses \Parsely\RemoteAPI\ContentSuggestions\Content_Suggestions_Base_API::get_request_options() + * @uses \Parsely\RemoteAPI\ContentSuggestions\Content_Suggestions_Base_API::post_request() */ public function test_get_suggestion(): void { $title = 'Lorem Ipsum is a random title'; $content = '

    Lorem ipsum dolor sit amet, consectetur adipiscing elit.

    '; + $persona = 'journalist'; + $style = 'neutral'; // Mock API result. - add_filter( 'pre_http_request', array( $this, 'mock_successful_suggest_meta_description_response' ), 10, 3 ); + add_filter( 'pre_http_request', array( $this, 'mock_successful_suggest_brief_response' ), 10, 3 ); // Test getting meta description. - $meta_description = self::$suggest_meta_description_api->get_suggestion( $title, $content ); + $brief = self::$suggest_brief_api->get_suggestion( $title, $content, $persona, $style ); - self::assertIsString( $meta_description ); - self::assertEquals( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', $meta_description ); + self::assertIsString( $brief ); + self::assertEquals( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', $brief ); // Remove mock. - remove_filter( 'pre_http_request', array( $this, 'mock_successful_suggest_meta_description_response' ) ); + remove_filter( 'pre_http_request', array( $this, 'mock_successful_suggest_brief_response' ) ); } } diff --git a/tests/Integration/RemoteAPI/content-suggestions/WriteTitleAPITest.php b/tests/Integration/RemoteAPI/content-suggestions/SuggestHeadlineAPITest.php similarity index 66% rename from tests/Integration/RemoteAPI/content-suggestions/WriteTitleAPITest.php rename to tests/Integration/RemoteAPI/content-suggestions/SuggestHeadlineAPITest.php index 60eabbe91..d6d879f46 100644 --- a/tests/Integration/RemoteAPI/content-suggestions/WriteTitleAPITest.php +++ b/tests/Integration/RemoteAPI/content-suggestions/SuggestHeadlineAPITest.php @@ -1,6 +1,6 @@ array( array( - 'apikey' => 'my-key', - 'secret' => 'my-secret', - 'persona' => 'journalist', - 'style' => 'neutral', - 'limit' => 3, + 'apikey' => 'my-key', ), Parsely::PUBLIC_SUGGESTIONS_API_BASE_URL . - '/write-title?apikey=my-key&limit=3&persona=journalist&secret=my-secret&style=neutral', + '/suggest-headline?apikey=my-key', ); } /** - * Mocks a successful HTTP response to the Content Suggestion write-titles + * Mocks a successful HTTP response to the Content Suggestion suggest-headline * API endpoint. * * @since 3.12.0 @@ -73,17 +69,17 @@ public function data_api_url(): iterable { * * @phpstan-ignore-next-line */ - public function mock_successful_write_titles_response( + public function mock_successful_suggest_headline_response( string $response, array $args, string $url ) { - if ( ! str_contains( $url, 'write-title' ) ) { + if ( ! str_contains( $url, 'suggest-headline' ) ) { return false; } $response = array( - 'titles' => array( + 'result' => array( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'Donec maximus metus sed urna maximus, et malesuada dui placerat.', 'Donec risus dui, dictum nec interdum eu, malesuada non diam.', @@ -109,9 +105,9 @@ public function mock_successful_write_titles_response( * * @since 3.12.0 * - * @covers \Parsely\RemoteAPI\ContentSuggestions\Write_Title_API::get_titles - * @uses \Parsely\RemoteAPI\ContentSuggestions\Write_Title_API::__construct - * @uses \Parsely\RemoteAPI\ContentSuggestions\Write_Title_API::get_titles + * @covers \Parsely\RemoteAPI\ContentSuggestions\Suggest_Headline_API::get_titles + * @uses \Parsely\RemoteAPI\ContentSuggestions\Suggest_Headline_API::__construct + * @uses \Parsely\RemoteAPI\ContentSuggestions\Suggest_Headline_API::get_titles * @uses \Parsely\RemoteAPI\ContentSuggestions\Content_Suggestions_Base_API::__construct * @uses \Parsely\RemoteAPI\ContentSuggestions\Content_Suggestions_Base_API::get_api_url * @uses \Parsely\RemoteAPI\Remote_API_Base::__construct @@ -124,15 +120,15 @@ public function test_get_titles(): void {

    '; // Mock API result. - add_filter( 'pre_http_request', array( $this, 'mock_successful_write_titles_response' ), 10, 3 ); + add_filter( 'pre_http_request', array( $this, 'mock_successful_suggest_headline_response' ), 10, 3 ); // Test getting three titles. - $titles = self::$write_title_api->get_titles( $content, 3 ); + $titles = self::$suggest_headline_api->get_titles( $content, 3 ); self::assertIsArray( $titles ); self::assertEquals( 3, count( $titles ) ); // Remove mock. - remove_filter( 'pre_http_request', array( $this, 'mock_successful_write_titles_response' ) ); + remove_filter( 'pre_http_request', array( $this, 'mock_successful_suggest_headline_response' ) ); } } diff --git a/tests/Integration/RemoteAPI/content-suggestions/SuggestLinkedReferenceAPITest.php b/tests/Integration/RemoteAPI/content-suggestions/SuggestLinkedReferenceAPITest.php new file mode 100644 index 000000000..49d3b4c84 --- /dev/null +++ b/tests/Integration/RemoteAPI/content-suggestions/SuggestLinkedReferenceAPITest.php @@ -0,0 +1,162 @@ + + */ + public function data_api_url(): iterable { + yield 'Basic (Expected data)' => array( + array( + 'apikey' => 'my-key', + ), + Parsely::PUBLIC_SUGGESTIONS_API_BASE_URL . + '/suggest-linked-reference?apikey=my-key', + ); + } + + /** + * Mocks a successful HTTP response to the Content Suggestion suggest-links + * API endpoint. + * + * @since 3.14.0 + * + * @param string $response The response to mock. + * @param array $args The arguments passed to the HTTP request. + * @param string $url The URL of the HTTP request. + * @return array|false The mocked response. + * + * @phpstan-ignore-next-line + */ + public function mock_successful_suggest_links_response( + string $response, + array $args, + string $url + ) { + if ( ! str_contains( $url, 'suggest-linked-reference' ) ) { + return false; + } + + $response = array( + 'result' => array( + array( + 'canonical_url' => 'http://example.com/article-1', + 'title' => 'Cool article 1', + 'text' => 'Lorem ipsum', + 'offset' => 0, + ), + array( + 'canonical_url' => 'http://example.com/article-2', + 'title' => 'A great article 2', + 'text' => 'maximus', + 'offset' => 0, + ), + array( + 'canonical_url' => 'http://example.com/article-3', + 'title' => 'Yet another great article 3', + 'text' => 'maximus', + 'offset' => 1, + ), + ), + ); + + return array( + 'headers' => array(), + 'cookies' => array(), + 'filename' => null, + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'status_code' => 200, + 'success' => true, + 'body' => $this->wp_json_encode( $response ), + ); + } + + /** + * Tests getting smart links suggestions from the API. + * + * @since 3.14.0 + * + * @covers \Parsely\RemoteAPI\ContentSuggestions\Suggest_Linked_Reference_API::get_links + * @uses \Parsely\Parsely::api_secret_is_set + * @uses \Parsely\Parsely::get_managed_credentials + * @uses \Parsely\Parsely::get_options + * @uses \Parsely\Parsely::get_site_id + * @uses \Parsely\Parsely::set_default_track_as_values + * @uses \Parsely\Parsely::site_id_is_set + * @uses \Parsely\RemoteAPI\Base_Endpoint_Remote::get_api_url + * @uses \Parsely\RemoteAPI\ContentSuggestions\Content_Suggestions_Base_API::get_request_options + * @uses \Parsely\RemoteAPI\ContentSuggestions\Content_Suggestions_Base_API::post_request + * @uses \Parsely\RemoteAPI\Base_Endpoint_Remote::validate_required_constraints + * @uses \Parsely\RemoteAPI\ContentSuggestions\Content_Suggestions_Base_API::get_api_url + */ + public function test_get_links(): void { + $content = '

    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec maximus metus sed urna maximus, + et malesuada dui placerat. Donec risus dui, dictum nec interdum eu, malesuada non diam. Curabitur + in erat eu nisi scelerisque tristique eu nec tortor. Nam fermentum rutrum mi id scelerisque. +

    '; + + // Mock API result. + add_filter( 'pre_http_request', array( $this, 'mock_successful_suggest_links_response' ), 10, 3 ); + + // Test getting three titles. + $suggested_links = self::$suggest_linked_reference_api->get_links( $content ); + + self::assertIsArray( $suggested_links ); + self::assertEquals( 3, count( $suggested_links ) ); + + // Assert the structure the suggested links, and if the object is a Link_Suggestion + // instance. + foreach ( $suggested_links as $suggested_link ) { + self::assertIsObject( $suggested_link ); + self::assertInstanceOf( 'Parsely\RemoteAPI\ContentSuggestions\Link_Suggestion', $suggested_link ); + } + + // Remove mock. + remove_filter( 'pre_http_request', array( $this, 'mock_successful_suggest_links_response' ) ); + } +} diff --git a/tests/Integration/RemoteAPITest.php b/tests/Integration/RemoteAPITest.php index 45010be05..9b8f5b84a 100644 --- a/tests/Integration/RemoteAPITest.php +++ b/tests/Integration/RemoteAPITest.php @@ -50,11 +50,14 @@ public static function set_up_before_class(): void { * @dataProvider data_api_url * @covers \Parsely\RemoteAPI\Related_API::get_api_url * @covers \Parsely\RemoteAPI\Analytics_Posts_API::get_api_url - * @uses \Parsely\Parsely::site_id_is_set + * @uses \Parsely\Endpoints\Base_Endpoint::__construct * @uses \Parsely\Parsely::api_secret_is_set - * @uses \Parsely\Parsely::get_site_id + * @uses \Parsely\Parsely::get_managed_credentials * @uses \Parsely\Parsely::get_options - * @uses \Parsely\Endpoints\Base_Endpoint::__construct + * @uses \Parsely\Parsely::get_site_id + * @uses \Parsely\Parsely::site_id_is_set + * @uses \Parsely\RemoteAPI\Base_Endpoint_Remote::validate_required_constraints + * @uses \Parsely\RemoteAPI\ContentSuggestions\Content_Suggestions_Base_API::get_api_url * * @param array $query Test query arguments. * @param string $url Expected generated URL. @@ -70,6 +73,7 @@ public function test_api_url( array $query, string $url ): void { * * @covers \Parsely\RemoteAPI\Remote_API_Cache::get_items * @covers \Parsely\RemoteAPI\Remote_API_Cache::__construct + * @uses \Parsely\RemoteAPI\Base_Endpoint_Remote::get_endpoint */ public function test_remote_api_cache_returns_cached_value(): void { $api_mock = $this->getMockBuilder( get_class( self::$remote_api ) ) @@ -114,6 +118,7 @@ public function test_remote_api_cache_returns_cached_value(): void { * * @covers \Parsely\RemoteAPI\Remote_API_Cache::get_items * @covers \Parsely\RemoteAPI\Remote_API_Cache::__construct + * @uses \Parsely\RemoteAPI\Base_Endpoint_Remote::get_endpoint */ public function test_caching_decorator_returns_uncached_value(): void { $api_mock = $this->getMockBuilder( get_class( self::$remote_api ) ) diff --git a/tests/Integration/content-helper/ContentHelperDashboardWidgetTest.php b/tests/Integration/content-helper/ContentHelperDashboardWidgetTest.php index 393b55f43..13032d131 100644 --- a/tests/Integration/content-helper/ContentHelperDashboardWidgetTest.php +++ b/tests/Integration/content-helper/ContentHelperDashboardWidgetTest.php @@ -87,7 +87,7 @@ protected function assert_enqueued_status( * @covers \Parsely\Content_Helper\Dashboard_Widget::get_script_id * @covers \Parsely\Content_Helper\Dashboard_Widget::get_style_id * @covers \Parsely\Content_Helper\Dashboard_Widget::run - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call + * @covers \Parsely\RemoteAPI\Analytics_Posts_API::is_available_to_current_user * @uses \Parsely\Parsely::__construct * @uses \Parsely\Endpoints\Base_Endpoint::__construct * @uses \Parsely\Utils\convert_endpoint_to_filter_key @@ -115,7 +115,7 @@ public function test_assets_do_not_get_enqueued_when_user_has_not_enough_capabil * @covers \Parsely\Content_Helper\Dashboard_Widget::get_script_id * @covers \Parsely\Content_Helper\Dashboard_Widget::get_style_id * @covers \Parsely\Content_Helper\Dashboard_Widget::run - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call + * @covers \Parsely\RemoteAPI\Analytics_Posts_API::is_available_to_current_user * @uses \Parsely\Parsely::__construct * @uses \Parsely\Endpoints\Base_Endpoint::__construct * @uses \Parsely\Utils\convert_endpoint_to_filter_key diff --git a/tests/Integration/content-helper/ContentHelperPostListStatsTest.php b/tests/Integration/content-helper/ContentHelperPostListStatsTest.php index 7674ed1fd..7070e67eb 100644 --- a/tests/Integration/content-helper/ContentHelperPostListStatsTest.php +++ b/tests/Integration/content-helper/ContentHelperPostListStatsTest.php @@ -113,7 +113,7 @@ protected function assert_enqueued_status( * @covers \Parsely\Content_Helper\Post_List_Stats::__construct * @covers \Parsely\Content_Helper\Post_List_Stats::get_feature_filter_name * @covers \Parsely\Content_Helper\Post_List_Stats::run - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call + * @covers \Parsely\RemoteAPI\Analytics_Posts_API::is_available_to_current_user * @covers \Parsely\Utils\convert_endpoint_to_filter_key * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set @@ -143,7 +143,7 @@ public function test_assets_do_not_get_enqueued_when_user_has_not_enough_capabil * @covers \Parsely\Content_Helper\Post_List_Stats::is_tracked_as_post_type * @covers \Parsely\Content_Helper\Post_List_Stats::run * @covers \Parsely\Content_Helper\Post_List_Stats::set_current_screen - * @covers \Parsely\Endpoints\Base_Endpoint::is_user_allowed_to_make_api_call + * @covers \Parsely\RemoteAPI\Analytics_Posts_API::is_available_to_current_user * @covers \Parsely\Utils\convert_endpoint_to_filter_key * @uses \Parsely\Parsely::__construct * @uses \Parsely\Parsely::api_secret_is_set diff --git a/tests/e2e/specs/content-helper/errors.spec.ts b/tests/e2e/specs/content-helper/errors.spec.ts index 6eaf590c5..8971617f9 100644 --- a/tests/e2e/specs/content-helper/errors.spec.ts +++ b/tests/e2e/specs/content-helper/errors.spec.ts @@ -5,15 +5,14 @@ import { INVALID_SITE_ID, VALID_API_SECRET, VALID_SITE_ID, - getTopRelatedPostsMessage, + getRelatedPostsMessage, setSiteKeys, } from '../../utils'; /** - * Tests for the errors presented by the PCH Editor Sidebar Related Top Posts - * panel. + * Tests for the errors presented by the PCH Editor Sidebar Related Posts panel. */ -describe( 'PCH Editor Sidebar Related Top Posts panel', () => { +describe( 'PCH Editor Sidebar Related Posts panel', () => { const contactMessage = 'Contact us about advanced plugin features and the Parse.ly dashboard.'; /** @@ -23,7 +22,7 @@ describe( 'PCH Editor Sidebar Related Top Posts panel', () => { it( 'Should display an error when an invalid Site ID is provided', async () => { await setSiteKeys( INVALID_SITE_ID, VALID_API_SECRET ); - expect( await getTopRelatedPostsMessage( '', '', 'author', 500, '.parsely-top-posts-descr' ) ) + expect( await getRelatedPostsMessage( '', '', 'author', 500, '.content-helper-error-message' ) ) .toMatch( 'Error: Forbidden' ); } ); @@ -34,7 +33,7 @@ describe( 'PCH Editor Sidebar Related Top Posts panel', () => { it( 'Should display a "Contact Us" message when the Site ID and API Secret are not provided', async () => { await setSiteKeys( '', '' ); - expect( await getTopRelatedPostsMessage() ).toMatch( contactMessage ); + expect( await getRelatedPostsMessage() ).toMatch( contactMessage ); } ); /** @@ -44,7 +43,7 @@ describe( 'PCH Editor Sidebar Related Top Posts panel', () => { it( 'Should display a "Contact Us" message when only the Site ID is provided', async () => { await setSiteKeys( VALID_SITE_ID, '' ); - expect( await getTopRelatedPostsMessage() ).toMatch( contactMessage ); + expect( await getRelatedPostsMessage() ).toMatch( contactMessage ); } ); /** @@ -54,7 +53,7 @@ describe( 'PCH Editor Sidebar Related Top Posts panel', () => { it( 'Should display a "Contact Us" message when only the API Secret is provided', async () => { await setSiteKeys( '', VALID_API_SECRET ); - expect( await getTopRelatedPostsMessage() ).toMatch( contactMessage ); + expect( await getRelatedPostsMessage() ).toMatch( contactMessage ); } ); /** @@ -64,6 +63,6 @@ describe( 'PCH Editor Sidebar Related Top Posts panel', () => { it( 'Should not display a "Contact Us" message when both the Site ID and API Secret are provided', async () => { await setSiteKeys( VALID_SITE_ID, VALID_API_SECRET ); - expect( await getTopRelatedPostsMessage() ).not.toMatch( contactMessage ); + expect( await getRelatedPostsMessage( '', '', 'author', 500, '.related-posts-descr' ) ).not.toMatch( contactMessage ); } ); } ); diff --git a/tests/e2e/specs/content-helper/related-top-posts-panel-filters.spec.ts b/tests/e2e/specs/content-helper/related-posts-panel-filters.spec.ts similarity index 64% rename from tests/e2e/specs/content-helper/related-top-posts-panel-filters.spec.ts rename to tests/e2e/specs/content-helper/related-posts-panel-filters.spec.ts index 1a3e7b193..26fe04724 100644 --- a/tests/e2e/specs/content-helper/related-top-posts-panel-filters.spec.ts +++ b/tests/e2e/specs/content-helper/related-posts-panel-filters.spec.ts @@ -11,17 +11,15 @@ import { import { VALID_API_SECRET, VALID_SITE_ID, - getTopRelatedPostsMessage, + getRelatedPostsMessage, setSiteKeys, setUserDisplayName, } from '../../utils'; /** - * Tests for the PCH Editor Sidebar Related Top Post filters. + * Tests for the PCH Editor Sidebar Related Post filters. */ -describe( 'PCH Editor Sidebar Related Top Post panel filters', () => { - const messageSelector = '.parsely-top-posts-descr'; - +describe( 'PCH Editor Sidebar Related Post panel filters', () => { /** * Prevents browser from locking with dialogs, logs in to WordPress, * activates the Parse.ly plugin, and sets valid site keys. @@ -38,23 +36,23 @@ describe( 'PCH Editor Sidebar Related Top Post panel filters', () => { it( 'Should attempt to fetch results when a Site ID and API Secret are provided', async () => { await setUserDisplayName( 'admin', '' ); - expect( await getTopRelatedPostsMessage( '', '', 'author', 500, messageSelector ) ) - .toMatch( `No top posts by author "admin" were found for the specified period and metric.` ); + expect( await getRelatedPostsMessage( '', '', 'author', 500, '.related-posts-empty' ) ) + .toMatch( `No related posts found.` ); } ); /** - * Verifies that the Related Top Posts panel will work correctly when a new + * Verifies that the Related Posts panel will work correctly when a new * taxonomy is added from within the WordPress Post Editor. * * Note: This test does not insert the taxonomy into the database before * selecting it in the WordPress Post Editor. As such, a delay in * intercepting the new value is expected, since it must first be stored - * into the database and then picked up by the Related Top Posts panel. + * into the database and then picked up by the Related Posts panel. */ it( 'Should work correctly when a taxonomy is added from within the WordPress Post Editor', async () => { const categoryName = 'Analytics That Matter'; - expect( await getTopRelatedPostsMessage( categoryName, '', 'section', 2000, messageSelector ) ) - .toMatch( `Top posts in section "${ categoryName }" in the last 30 days.` ); + expect( await getRelatedPostsMessage( categoryName, '', 'section', 2000, '.related-posts-descr' ) ) + .toMatch( `Top related posts in the “${ categoryName }” section in the last 7 days.` ); } ); } ); diff --git a/tests/e2e/specs/content-helper/top-bar-icon.spec.ts b/tests/e2e/specs/content-helper/top-bar-icon.spec.ts index 2a91c502f..20378c1dc 100644 --- a/tests/e2e/specs/content-helper/top-bar-icon.spec.ts +++ b/tests/e2e/specs/content-helper/top-bar-icon.spec.ts @@ -12,18 +12,19 @@ import { import { VALID_API_SECRET, VALID_SITE_ID, + setSidebarPanelExpanded, setSiteKeys, } from '../../utils'; // Selectors. -const pluginButton = 'button[aria-label="Parse.ly Editor Sidebar"]'; +const pluginButton = 'button[aria-label="Parse.ly"]'; /** * Tests for the PCH Editor Sidebar top bar icon. */ describe( 'PCH Editor Sidebar top bar icon in the WordPress Post Editor', () => { - const postNotPublishedMessage = 'SettingsPeriodLast 10 MinutesLast HourLast 2 HoursLast 4 HoursLast 24 HoursLast 7 DaysLast 30 DaysMetricPage ViewsAvg. TimePerformance DetailsThis post is not published, so its details are unavailable.Related Top Posts'; - const emptyCredentialsMessage = 'SettingsPeriodLast 10 MinutesLast HourLast 2 HoursLast 4 HoursLast 24 HoursLast 7 DaysLast 30 DaysMetricPage ViewsAvg. TimePerformance DetailsContact us about advanced plugin features and the Parse.ly dashboard.Existing Parse.ly customers can enable this feature by setting their Site ID and API Secret in wp-parsely options.Related Top Posts'; + const noRelatedPostsMessage = 'No related posts found.'; + const emptyCredentialsMessage = 'Contact us about advanced plugin features and the Parse.ly dashboard.Existing Parse.ly customers can enable this feature by setting their Site ID and API Secret in wp-parsely options.'; /** * Verifies that the top bar icon gets displayed when the Site ID and API @@ -57,8 +58,11 @@ describe( 'PCH Editor Sidebar top bar icon in the WordPress Post Editor', () => * API Secret are provided. */ it( 'Should be displayed when both the Site ID and API Secret are provided', async () => { - expect( await testContentHelperIcon( VALID_SITE_ID, VALID_API_SECRET ) ) - .toMatch( postNotPublishedMessage ); + expect( await testContentHelperIcon( + VALID_SITE_ID, VALID_API_SECRET, + '.related-posts-empty' + ) ) + .toMatch( noRelatedPostsMessage ); } ); /** @@ -91,33 +95,35 @@ describe( 'PCH Editor Sidebar top bar icon in the WordPress Post Editor', () => * Tests the top bar icon by clicking on it and verifying that the PCH Editor * Sidebar opens. * - * @param {string} siteId - * @param {string} apiSecret - * @return {string} Text content found in the PCH Editor Sidebar. + * @param { string } siteId The Site ID to use for the test. + * @param { string } apiSecret The API Secret to use for the test. + * @param { string } selector The selector from which to get the text content. + * + * @return { string } Text content found in the PCH Editor Sidebar. */ -async function testContentHelperIcon( siteId: string, apiSecret: string ) { +async function testContentHelperIcon( + siteId: string, apiSecret: string, selector = '.content-helper-error-message' +) { + const contentHelperMessageSelector = '.wp-parsely-content-helper div.components-panel__body.is-opened ' + selector; + await setSiteKeys( siteId, apiSecret ); await createNewPost(); - // Open the sidebar by clicking on the icon, to verify that it is visible and - // working as expected. - await page.waitForSelector( pluginButton, { visible: true } ); - const toggleSidebarButton = await page.$( - pluginButton - ); - if ( toggleSidebarButton ) { - await toggleSidebarButton.click(); - } + // Click the top bar icon. + await page.waitForSelector( pluginButton ); + await page.click( pluginButton ); - // Get the text content of the sidebar. - await page.waitForSelector( 'div.wp-parsely-content-helper', { visible: true } ); + // Expand the Related Posts panel and get its text content. + setSidebarPanelExpanded( 'Related Posts', true ); + await page.waitForSelector( contentHelperMessageSelector ); + await page.waitForFunction( // Wait for the message to appear. + 'document.querySelector("' + contentHelperMessageSelector + '").innerText.length > 0', + { polling: 'mutation', timeout: 5000 } + ); const text = await page.$eval( - 'div.wp-parsely-content-helper', - ( element: Element ) => element.textContent + contentHelperMessageSelector, + ( element: Element ): string => element.textContent ?? '' ); - // Close the sidebar for the next test. - await toggleSidebarButton?.click(); - return text; } diff --git a/tests/e2e/specs/front-end-metadata.spec.ts b/tests/e2e/specs/front-end-metadata.spec.ts index 14754b68f..1f68a5f0c 100644 --- a/tests/e2e/specs/front-end-metadata.spec.ts +++ b/tests/e2e/specs/front-end-metadata.spec.ts @@ -61,7 +61,7 @@ describe( 'Front end metadata insertion', () => { expect( content ).not.toContain( ' { + it( 'Should insert JSON-LD on post', async () => { await setMetadataFormat( 'json_ld' ); await page.goto( createURL( '/', '?p=1' ) ); @@ -74,6 +74,19 @@ describe( 'Front end metadata insertion', () => { expect( content ).not.toContain( ' { + await setMetadataFormat( 'json_ld' ); + + await page.goto( createURL( '/', '?p=2' ) ); + + const content = await page.content(); + + expect( content ).toContain( '