diff --git a/admin/class-admin-asset-manager.php b/admin/class-admin-asset-manager.php index d9c16f15333..5904b2d592d 100644 --- a/admin/class-admin-asset-manager.php +++ b/admin/class-admin-asset-manager.php @@ -482,6 +482,7 @@ protected function load_renamed_scripts() { 'feature-flag' => 'feature-flag-package', 'helpers' => 'helpers-package', 'jed' => 'jed-package', + 'chart.js' => 'chart.js-package', 'legacy-components' => 'components-package', 'network-admin-script' => 'network-admin', 'redux' => 'redux-package', diff --git a/admin/views/redirects.php b/admin/views/redirects.php index f37a264bfa9..bae929682f4 100644 --- a/admin/views/redirects.php +++ b/admin/views/redirects.php @@ -17,7 +17,7 @@

- + get_post_type_label( get_post_type( $post_id ) ); /* translators: %1$s expands to the translated name of the post type. */ - $first_sentence = sprintf( __( 'You just trashed a %1$s.', 'wordpress-seo' ), $post_label ); - $message = $this->get_message( $first_sentence, 'trashed', $post_label ); + $first_sentence = sprintf( __( 'You just trashed a %1$s.', 'wordpress-seo' ), $post_label ); + $second_sentence = __( 'Search engines and other websites can still send traffic to your trashed content.', 'wordpress-seo' ); + $message = $this->get_message( $first_sentence, $second_sentence ); $this->add_notification( $message ); } @@ -85,8 +86,9 @@ public function detect_post_delete( $post_id ) { $post_label = $this->get_post_type_label( get_post_type( $post_id ) ); /* translators: %1$s expands to the translated name of the post type. */ - $first_sentence = sprintf( __( 'You just deleted a %1$s.', 'wordpress-seo' ), $post_label ); - $message = $this->get_message( $first_sentence, 'deleted', $post_label ); + $first_sentence = sprintf( __( 'You just deleted a %1$s.', 'wordpress-seo' ), $post_label ); + $second_sentence = __( 'Search engines and other websites can still send traffic to your deleted content.', 'wordpress-seo' ); + $message = $this->get_message( $first_sentence, $second_sentence ); $this->add_notification( $message ); } @@ -107,8 +109,9 @@ public function detect_term_delete( $term_taxonomy_id ) { $term_label = $this->get_taxonomy_label_for_term( $term->term_id ); /* translators: %1$s expands to the translated name of the term. */ - $first_sentence = sprintf( __( 'You just deleted a %1$s.', 'wordpress-seo' ), $term_label ); - $message = $this->get_message( $first_sentence, 'deleted', $term_label ); + $first_sentence = sprintf( __( 'You just deleted a %1$s.', 'wordpress-seo' ), $term_label ); + $second_sentence = __( 'Search engines and other websites can still send traffic to your deleted content.', 'wordpress-seo' ); + $message = $this->get_message( $first_sentence, $second_sentence ); $this->add_notification( $message ); } @@ -209,17 +212,15 @@ protected function check_visible_post_status( $post_status ) { * Returns the message around changed URLs. * * @param string $first_sentence The first sentence of the notification. - * @param string $action The action performed, either "deleted" or "trashed". - * @param string $object_label The label of the object that was deleted or trashed. + * @param string $second_sentence The second sentence of the notification. * * @return string The full notification. */ - protected function get_message( $first_sentence, $action, $object_label ) { + protected function get_message( $first_sentence, $second_sentence ) { return '

' . __( 'Make sure you don\'t miss out on traffic!', 'wordpress-seo' ) . '

' . '

' . $first_sentence - /* translators: %1$s expands to either "deleted" or "trashed". %2$s expands to the name of the post or term. */ - . ' ' . sprintf( __( 'Search engines and other websites can still send traffic to your %1$s %2$s.', 'wordpress-seo' ), $action, $object_label ) + . ' ' . $second_sentence . ' ' . __( 'You should create a redirect to ensure your visitors do not get a 404 error when they click on the no longer working URL.', 'wordpress-seo' ) /* translators: %s expands to Yoast SEO Premium */ . ' ' . sprintf( __( 'With %s, you can easily create such redirects.', 'wordpress-seo' ), 'Yoast SEO Premium' ) diff --git a/config/dependency-injection/deprecated-classes.php b/config/dependency-injection/deprecated-classes.php index 0a20094b0f6..f731d54932b 100644 --- a/config/dependency-injection/deprecated-classes.php +++ b/config/dependency-injection/deprecated-classes.php @@ -35,6 +35,8 @@ use Yoast\WP\SEO\Integrations\Schema_Blocks; use Yoast\WP\SEO\Integrations\Third_Party\CoAuthors_Plus; use Yoast\WP\SEO\Integrations\Third_Party\The_Events_Calendar; +use Yoast\WP\SEO\Integrations\Third_Party\Wincher; +use Yoast\WP\SEO\Integrations\Third_Party\Wordproof_Integration_Toggle; use Yoast\WP\SEO\Routes\Indexables_Page_Route; use Yoast\WP\SEO\Schema_Templates\Assets\Icons; @@ -56,6 +58,8 @@ Schema_Blocks::class => '20.5', Icons::class => '20.5', Old_Premium_Integration::class => '20.10', + Wincher::class => '21.6', + Wordproof_Integration_Toggle::class => '21.6', ]; foreach ( $deprecated_classes as $original_class => $version ) { diff --git a/config/jenkins/Jenkinsfile_artifact b/config/jenkins/Jenkinsfile_artifact deleted file mode 100644 index e4d13330bc7..00000000000 --- a/config/jenkins/Jenkinsfile_artifact +++ /dev/null @@ -1,25 +0,0 @@ -node( 'docker-agent' ) { - checkout scm - env.GIT_COMMIT = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'") - docker.withServer( 'tcp://172.17.0.1:2375' ) { - def ubuntu = docker.image( 'yoastseo/docker-php-composer-node' ) - ubuntu.pull() - ubuntu.inside() { - stage( 'Set Version'){ - // sed using '#' as separators. overwriting php files with the new plugin name. - sh "SEARCH_STRING=\"[Pp]lugin [Nn]ame.*\" && REPLACE_STRING=\'* Plugin Name: Yoast SEO (beta) | ${env.BRANCH_NAME} | ${env.GIT_COMMIT}\' && sed -i \"s#\$SEARCH_STRING#\$REPLACE_STRING#\" wp-seo.php" - } - stage( 'Build' ){ - sh 'composer install' - sh 'yarn' - sh 'grunt build' - sh 'grunt artifact' - } - stage( 'Expose Artifact' ) { - def BRANCH_NAME = sh(returnStdout: true, script: "echo ${env.BRANCH_NAME} | sed -e s#/#-# | tr --delete '\n'") - sh "mv artifact.zip wordpress-seo-${BRANCH_NAME}.zip" - archiveArtifacts artifacts: "wordpress-seo-${BRANCH_NAME}.zip", fingerprint: true - } - } - } -} \ No newline at end of file diff --git a/config/webpack/externals.js b/config/webpack/externals.js index 03f564ae81e..2a26a0926d2 100644 --- a/config/webpack/externals.js +++ b/config/webpack/externals.js @@ -1,11 +1,13 @@ -const { camelCaseDash } = require( "@wordpress/dependency-extraction-webpack-plugin/lib/util" ); +const { + camelCaseDash, +} = require("@wordpress/dependency-extraction-webpack-plugin/lib/util"); /** * Yoast dependencies, declared as such in the package.json. */ -const { dependencies } = require( "../../packages/js/package" ); -const legacyYoastPackages = [ "yoast-components", "yoastseo" ]; -const additionalPackages = [ +const { dependencies } = require("../../packages/js/package"); +const legacyYoastPackages = ["yoast-components", "yoastseo"]; +const additionalPackages = [ "draft-js", "styled-components", "jed", @@ -13,26 +15,26 @@ const additionalPackages = [ "redux", "@reduxjs/toolkit", "react-helmet", + "chart.js", ]; const YOAST_PACKAGE_NAMESPACE = "@yoast/"; // Fetch all packages from the dependencies list. -const yoastPackages = Object.keys( dependencies ) - .filter( - ( packageName ) => - packageName.startsWith( YOAST_PACKAGE_NAMESPACE ) || - legacyYoastPackages.includes( packageName ) || - additionalPackages.includes( packageName ) - ); +const yoastPackages = Object.keys(dependencies).filter( + (packageName) => + packageName.startsWith(YOAST_PACKAGE_NAMESPACE) || + legacyYoastPackages.includes(packageName) || + additionalPackages.includes(packageName) +); /** * Convert Yoast packages to externals configuration. */ -const yoastExternals = yoastPackages.reduce( ( memo, packageName ) => { - let useablePackageName = packageName.replace( YOAST_PACKAGE_NAMESPACE, "" ); +const yoastExternals = yoastPackages.reduce((memo, packageName) => { + let useablePackageName = packageName.replace(YOAST_PACKAGE_NAMESPACE, ""); - switch ( useablePackageName ) { + switch (useablePackageName) { case "components": useablePackageName = "components-new"; break; @@ -47,9 +49,9 @@ const yoastExternals = yoastPackages.reduce( ( memo, packageName ) => { break; } - memo[ packageName ] = camelCaseDash( useablePackageName ); + memo[packageName] = camelCaseDash(useablePackageName); return memo; -}, {} ); +}, {}); module.exports = { YOAST_PACKAGE_NAMESPACE, diff --git a/config/webpack/webpack.config.base.js b/config/webpack/webpack.config.base.js index 64ca5a24e32..84b40e3c172 100644 --- a/config/webpack/webpack.config.base.js +++ b/config/webpack/webpack.config.base.js @@ -10,7 +10,7 @@ const { yoastExternals } = require( "./externals" ); let analyzerPort = 8888; module.exports = function( { entry, output, combinedOutputFile, cssExtractFileName } ) { - const exclude = /node_modules[/\\](?!(yoast-components|gutenberg|yoastseo|@wordpress|@yoast|parse5)[/\\]).*/; + const exclude = /node_modules[/\\](?!(yoast-components|gutenberg|yoastseo|@wordpress|@yoast|parse5|chart.js)[/\\]).*/; // The index of the babel-loader rule. let ruleIndex = 0; if ( process.env.NODE_ENV !== "production" ) { diff --git a/css/src/introductions.css b/css/src/introductions.css index a047f9f9d91..aa72b205234 100644 --- a/css/src/introductions.css +++ b/css/src/introductions.css @@ -6,4 +6,11 @@ focus:yst-outline-none focus:yst-ring-offset-0; } + .yst-introduction-modal-panel { + background-image: linear-gradient(180deg, rgba(166, 30, 105, 0.25) 10%, rgba(255, 255, 255, 0.25) 50%); + } + .yst-introduction-modal-uppercase{ + letter-spacing: 0.8px; + @apply yst-uppercase yst-text-slate-500; + } } diff --git a/css/src/modal.css b/css/src/modal.css index 714ac12d7ec..e3484058d3e 100644 --- a/css/src/modal.css +++ b/css/src/modal.css @@ -64,6 +64,7 @@ @media (min-width: 600px) { border-radius: 8px; + max-height: calc( 100% - 48px ); } } @@ -80,6 +81,10 @@ margin: 0; } +.yoast-gutenberg-modal .components-modal__content .components-modal__header { + border-bottom: 1px solid #e2e8f0 !important; +} + .yoast-gutenberg-modal .components-modal__icon-container { display: inline-flex; } diff --git a/package.json b/package.json index e9ac12c7200..4ec7ed46108 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "typescript": "^4.2.4" }, "yoast": { - "pluginVersion": "21.5-RC1" + "pluginVersion": "21.5-RC4" }, "version": "0.0.0" } diff --git a/packages/babel-preset/index.js b/packages/babel-preset/index.js index b9b6601be29..51798047fda 100644 --- a/packages/babel-preset/index.js +++ b/packages/babel-preset/index.js @@ -3,7 +3,11 @@ module.exports = ( api ) => { return { presets: [ "@wordpress/babel-preset-default" ], - plugins: [ "@babel/plugin-proposal-optional-chaining", "@babel/plugin-transform-runtime" ], + plugins: [ + "@babel/plugin-proposal-optional-chaining", + "@babel/plugin-transform-runtime", + "@babel/plugin-transform-class-properties", + ], sourceType: "unambiguous", }; }; diff --git a/packages/babel-preset/package.json b/packages/babel-preset/package.json index cf427199114..df81cbc8084 100644 --- a/packages/babel-preset/package.json +++ b/packages/babel-preset/package.json @@ -13,6 +13,7 @@ "private": false, "dependencies": { "@babel/plugin-proposal-optional-chaining": "^7.17.12", + "@babel/plugin-transform-class-properties": "^7.22.5", "@babel/plugin-transform-runtime": "^7.17.12", "@wordpress/babel-preset-default": "^6.13.0" }, diff --git a/packages/js/package.json b/packages/js/package.json index ea70d872a91..f130756f2d6 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -11,8 +11,9 @@ "@draft-js-plugins/mention": "^5.0.0", "@headlessui/react": "^1.7.8", "@heroicons/react": "^1.0.6", - "@wordpress/api-fetch": "^6.13.0", + "@reduxjs/toolkit": "^1.8.3", "@wordpress/a11y": "^2.15.1", + "@wordpress/api-fetch": "^6.13.0", "@wordpress/block-editor": "^5.3.1", "@wordpress/blocks": "^11.1.2", "@wordpress/components": "^13.0.3", @@ -40,9 +41,13 @@ "@yoast/ui-library": "^3.2.1", "a11y-speak": "git+https://github.com/Yoast/a11y-speak.git#master", "babel-polyfill": "^6.26.0", + "bowser": "^2.11.0", + "chart.js": "^4.2.1", + "chartjs-adapter-moment": "^1.0.1", "classnames": "^2.3.2", "draft-js": "^0.11.7", "find-with-regex": "~1.0.2", + "formik": "^2.2.9", "interpolate-components": "^1.1.0", "jed": "^1.1.1", "lodash": "^4.17.21", @@ -51,22 +56,20 @@ "moment-duration-format": "^2.2.2", "prop-types": "^15.5.10", "react-animate-height": "^2.0.23", + "react-aria-live": "^2.0.5", + "react-chartjs-2": "^5.2.0", "react-helmet": "^6.1.0", + "react-hotkeys-hook": "^4.0.5", "react-intl": "^2.4.0", "react-redux": "^5.0.6", + "react-router-dom": "^6.3.0", "react-select": "^3.1.0", "redux": "^3.7.2", "redux-thunk": "^2.2.0", "styled-components": "^5.3.6", "yoast-components": "^5.24.0", "yoastseo": "^1.91.1", - "formik": "^2.2.9", - "@reduxjs/toolkit": "^1.8.3", - "react-router-dom": "^6.3.0", - "yup": "^0.32.11", - "bowser": "^2.11.0", - "react-hotkeys-hook": "^4.0.5", - "react-aria-live": "^2.0.5" + "yup": "^0.32.11" }, "devDependencies": { "@babel/core": "^7.17.9", diff --git a/packages/js/src/ai-generator/initialize.js b/packages/js/src/ai-generator/initialize.js index 974645e9661..e048379933e 100644 --- a/packages/js/src/ai-generator/initialize.js +++ b/packages/js/src/ai-generator/initialize.js @@ -32,7 +32,7 @@ const AiGeneratorUpsell = ( { fieldId } ) => { { __( "Use AI", "wordpress-seo" ) } - + diff --git a/packages/js/src/components/AdvancedSettings.js b/packages/js/src/components/AdvancedSettings.js index 07d37428a9f..710dde707fc 100644 --- a/packages/js/src/components/AdvancedSettings.js +++ b/packages/js/src/components/AdvancedSettings.js @@ -31,7 +31,7 @@ const getNoIndexOptions = ( editorContext ) => { return [ { name: sprintf( - /* Translators: %s translates to "yes" or "no", %s translates to the Post Label in plural form */ + /* translators: the first %s translates to "yes" or "no", the second %s translates to the content type label in plural form */ __( "%s (current default for %s)", "wordpress-seo" ), noIndex, editorContext.postTypeNamePlural @@ -45,7 +45,7 @@ const getNoIndexOptions = ( editorContext ) => { return [ { name: sprintf( - /* Translators: %s translates to the "yes" or "no" ,%s translates to the Post Label in plural form */ + /* translators: the first %s translates to "yes" or "no", the second %s translates to the content type label in plural form */ __( "%s (current default for %s)", "wordpress-seo" ), noIndex, editorContext.postTypeNamePlural @@ -81,12 +81,7 @@ const MetaRobotsNoIndex = ( { noIndex, onNoIndexChange, editorContext, isPrivate } + { + options.map( opt => { + return ( + + ); + } ) + } + + ); +}; + +WincherPeriodPicker.propTypes = { + onSelect: PropTypes.func.isRequired, + selected: PropTypes.object, + options: PropTypes.array.isRequired, + isLoggedIn: PropTypes.bool.isRequired, +}; + /** * Creates the table content. * @@ -237,10 +324,12 @@ const Title = styled.div` */ const TableContent = ( props ) => { const { + trackedKeyphrases, isLoggedIn, keyphrases, shouldTrackAll, permalink, + historyDaysLimit, } = props; if ( ! permalink && isLoggedIn ) { @@ -251,20 +340,93 @@ const TableContent = ( props ) => { return ; } + const historyLimitDate = moment( START_OF_TODAY ).subtract( historyDaysLimit, "days" ); + + const periodOptions = WINCHER_PERIOD_OPTIONS.filter( + opt => moment( opt.value ).isSameOrAfter( historyLimitDate ) + ); + + const defaultPeriod = orderBy( periodOptions, opt => opt.defaultIndex, "desc" )[ 0 ]; + + const [ period, setPeriod ] = useState( defaultPeriod ); + + const [ selectedKeyphrases, setSelectedKeyphrases ] = useState( [] ); + + const isChartShown = selectedKeyphrases.length > 0; + + const trackedKeyphrasesPrev = usePrevious( trackedKeyphrases ); + + useEffect( () => { + if ( ! isEmpty( trackedKeyphrases ) && Object.values( trackedKeyphrases ).length !== ( trackedKeyphrasesPrev || [] ).length ) { + const keywords = Object.values( trackedKeyphrases ).map( keyphrase => keyphrase.keyword ); + setSelectedKeyphrases( keywords ); + } + }, [ trackedKeyphrases, trackedKeyphrasesPrev ] ); + + useEffect( () => { + setPeriod( defaultPeriod ); + }, [ defaultPeriod?.name ] ); + + const onSelectPeriod = useCallback( ( event ) => { + const option = WINCHER_PERIOD_OPTIONS.find( opt => opt.value === event.target.value ); + if ( option ) { + setPeriod( option ); + } + }, [ setPeriod ] ); + + const chartData = useMemo( () => { + if ( isEmpty( selectedKeyphrases ) ) { + return []; + } + if ( isEmpty( trackedKeyphrases ) ) { + return []; + } + return Object.values( trackedKeyphrases ) + .filter( keyphrase => !! keyphrase?.position?.history ) + .map( keyphrase => ( { + label: keyphrase.keyword, + data: keyphrase.position.history, + selected: selectedKeyphrases.includes( keyphrase.keyword ) && ! isEmpty( keyphrase.position?.history ), + } ) ); + }, [ selectedKeyphrases, trackedKeyphrases ] ); + return

{ __( "You can enable / disable tracking the SEO performance for each keyphrase below.", "wordpress-seo" ) }

{ isLoggedIn && shouldTrackAll && } - + + + + + + + + + ; }; TableContent.propTypes = { + trackedKeyphrases: PropTypes.object, keyphrases: PropTypes.array.isRequired, isLoggedIn: PropTypes.bool.isRequired, shouldTrackAll: PropTypes.bool.isRequired, permalink: PropTypes.string.isRequired, + historyDaysLimit: PropTypes.number, }; /** @@ -283,11 +445,12 @@ export default function WincherSEOPerformance( props ) { const onLoginCallback = useCallback( () => { onLoginOpen( props ); }, [ onLoginOpen, props ] ); + const trackingInfo = useTrackingInfo( isLoggedIn ); return ( { isNewlyAuthenticated && } - { isLoggedIn && } + { isLoggedIn && } { __( "SEO performance", "wordpress-seo" ) } @@ -302,12 +465,13 @@ export default function WincherSEOPerformance( props ) { <ConnectToWincher isLoggedIn={ isLoggedIn } onLogin={ onLoginCallback } /> <GetUserMessage { ...props } onLogin={ onLoginCallback } /> - <TableContent { ...props } /> + <TableContent { ...props } historyDaysLimit={ trackingInfo?.historyDays || 0 } /> </Wrapper> ); } WincherSEOPerformance.propTypes = { + trackedKeyphrases: PropTypes.object, addTrackedKeyphrase: PropTypes.func.isRequired, isLoggedIn: PropTypes.bool, isNewlyAuthenticated: PropTypes.bool, @@ -315,13 +479,16 @@ WincherSEOPerformance.propTypes = { response: PropTypes.object, shouldTrackAll: PropTypes.bool, permalink: PropTypes.string, + historyDaysLimit: PropTypes.number, }; WincherSEOPerformance.defaultProps = { + trackedKeyphrases: null, isLoggedIn: false, isNewlyAuthenticated: false, keyphrases: [], response: {}, shouldTrackAll: false, permalink: "", + historyDaysLimit: 0, }; diff --git a/packages/js/src/components/WincherSEOPerformanceModal.js b/packages/js/src/components/WincherSEOPerformanceModal.js index 07b1f3cc8a5..29922bbbd71 100644 --- a/packages/js/src/components/WincherSEOPerformanceModal.js +++ b/packages/js/src/components/WincherSEOPerformanceModal.js @@ -6,6 +6,10 @@ import { useSvgAria } from "@yoast/ui-library/src"; import PropTypes from "prop-types"; import styled from "styled-components"; +/* Yoast dependencies */ +import { colors } from "@yoast/style-guide"; +import { Collapsible } from "@yoast/components"; + /* Internal dependencies */ import { ModalContainer } from "./modals/Container"; import Modal from "./modals/Modal"; @@ -21,6 +25,17 @@ const StyledHeroIcon = styled( ChartBarIcon )` margin: 3px; `; +const MetaboxModalButton = styled( Collapsible )` + h2 > button { + padding-left: 24px; + padding-top: 16px; + + &:hover { + background-color: #f0f0f0; + } + } +`; + /** * Handles the click event on the "Track SEO performance" button. * @@ -33,7 +48,13 @@ export function openModal( props ) { if ( ! keyphrases.length ) { // This is fragile, should replace with a real React ref. - document.querySelector( "#focus-keyword-input-sidebar" ).focus(); + let input = document.querySelector( "#focus-keyword-input-metabox" ); + + // In elementor we use input-sidebar + if ( ! input ) { + input = document.querySelector( "#focus-keyword-input-sidebar" ); + } + input.focus(); onNoKeyphraseSet(); return; @@ -106,6 +127,19 @@ export default function WincherSEOPerformanceModal( props ) { onClick={ onModalOpen } /> } + + { location === "metabox" && <MetaboxModalButton + hasPadding={ false } + hasSeparator={ true } + suffixIconCollapsed={ { + icon: "pencil-square", + color: colors.$black, + size: "20px", + } } + id={ `wincher-open-button-${location}` } + title={ title } + onToggle={ onModalOpen } + /> } </Fragment> ); } diff --git a/packages/js/src/components/WincherTableRow.js b/packages/js/src/components/WincherTableRow.js index dee87fc0e69..5bee5aa2298 100644 --- a/packages/js/src/components/WincherTableRow.js +++ b/packages/js/src/components/WincherTableRow.js @@ -6,14 +6,66 @@ import { isEmpty } from "lodash"; import moment from "moment"; /* Yoast dependencies */ -import { SvgIcon, Toggle } from "@yoast/components"; -import { makeOutboundLink } from "@yoast/helpers"; +import { Checkbox, SvgIcon, Toggle, ButtonStyledLink } from "@yoast/components"; /* Internal dependencies */ import AreaChart from "./AreaChart"; import WincherSEOPerformanceLoading from "./modals/WincherSEOPerformanceLoading"; +import styled from "styled-components"; -const ViewLink = makeOutboundLink(); +export const CaretIcon = styled( SvgIcon )` + margin-left: 2px; + flex-shrink: 0; + rotate: ${ props => props.isImproving ? "-90deg" : "90deg" }; +`; + +export const PositionChangeValue = styled.span` + color: ${ props => props.isImproving ? "#69AB56" : "#DC3332" }; + font-size: 13px; + font-weight: 600; + line-height: 20px; + margin-right: 2px; + margin-left: 12px; +`; + +export const SelectKeyphraseCheckboxWrapper = styled.td` + padding-right: 0 !important; + + & > div { + margin: 0px; + } +`; + +export const KeyphraseTdWrapper = styled.td` + padding-left: 2px !important; +`; + +export const TrackingTdWrapper = styled.td.attrs( { className: "yoast-table--nopadding" } )` + & > div { + justify-content: center; + } +`; + +const PositionAndViewLinkWrapper = styled.div` + display: flex; + align-items: center; +`; + +const PositionOverTimeButton = styled.button` + background: none; + color: inherit; + border: none; + padding: 0; + font: inherit; + cursor: pointer; + outline: inherit; + display: flex; + align-items: center; +`; + +const WincherTableRowElement = styled.tr` + background-color: ${ props => props.isEnabled ? "#FFFFFF" : "#F9F9F9" } !important; +`; /** * Transforms the Wincher Position data to x/y points for the SVG area chart. @@ -74,7 +126,6 @@ export function PositionOverTimeChart( { chartData } ) { strokeWidth={ 1.8 } strokeColor="#498afc" fillColor="#ade3fc" - className="yoast-related-keyphrases-modal__chart" mapChartDataToTableData={ mapAreaChartDataToTableData } dataTableCaption={ __( "Keyphrase position in the last 90 days on a scale from 0 to 100.", "wordpress-seo" ) @@ -132,6 +183,45 @@ export function getKeyphrasePosition( keyphrase ) { return keyphrase.position.value; } +/** + * Humanize the last updated date string + * + * @param {string} dateString The date string to format. + * + * @returns {string} The formatted last updated date. + */ +const formatLastUpdated = ( dateString ) => moment( dateString ).fromNow(); + +/** + * Displays the position over time cell. + * + * @param {object} rowData The position over time data. + * + * @returns {wp.Element} The position over time table cell. + */ +export const PositionOverTimeCell = ( { rowData } ) => { + if ( ! rowData?.position?.change ) { + return <PositionOverTimeChart chartData={ rowData } />; + } + + const isImproving = rowData.position.change < 0; + return ( + <Fragment> + <PositionOverTimeChart chartData={ rowData } /> + <PositionChangeValue isImproving={ isImproving }>{ Math.abs( rowData.position.change ) }</PositionChangeValue> + <CaretIcon + icon={ "caret-right" } + color={ isImproving ? "#69AB56" : "#DC3332" } + size={ "14px" } isImproving={ isImproving } + /> + </Fragment> + ); +}; + +PositionOverTimeCell.propTypes = { + rowData: PropTypes.object, +}; + /** * Gets the positional data based on the current UI state and returns the appropiate UI element. * @@ -140,7 +230,16 @@ export function getKeyphrasePosition( keyphrase ) { * @returns {wp.Element} The rendered element. */ export function getPositionalDataByState( props ) { - const { rowData, websiteId } = props; + const { rowData, websiteId, keyphrase, onSelectKeyphrases } = props; + + /** + * Fires when click on position over time + * + * @returns {void} + */ + const onPositionOverTimeClick = useCallback( () => { + onSelectKeyphrases( [ keyphrase ] ); + }, [ onSelectKeyphrases, keyphrase ] ); const isEnabled = ! isEmpty( rowData ); const hasFreshData = rowData && rowData.updated_at && moment( rowData.updated_at ) >= moment().subtract( 7, "days" ); @@ -152,34 +251,35 @@ export function getPositionalDataByState( props ) { if ( ! isEnabled ) { return ( - <Fragment> - <td>?</td> - <td className="yoast-table--nopadding">?</td> - <td className="yoast-table--nobreak" /> - </Fragment> + <td className="yoast-table--nopadding" colSpan="3"> + <i>{ __( "Activate tracking to show the ranking position", "wordpress-seo" ) }</i> + </td> ); } if ( ! hasFreshData ) { return ( - <Fragment> - <td className="yoast-table--nopadding" colSpan="3"> - <WincherSEOPerformanceLoading /> - </td> - </Fragment> + <td className="yoast-table--nopadding" colSpan="3"> + <WincherSEOPerformanceLoading /> + </td> ); } return ( <Fragment> - <td>{ getKeyphrasePosition( rowData ) }</td> - <td className="yoast-table--nopadding">{ <PositionOverTimeChart chartData={ rowData } /> }</td> - <td className="yoast-table--nobreak"> - { - <ViewLink href={ viewLinkURL }> + <td> + <PositionAndViewLinkWrapper> + { getKeyphrasePosition( rowData ) } + <ButtonStyledLink variant="secondary" href={ viewLinkURL } style={ { height: 28, marginLeft: 12 } }> { __( "View", "wordpress-seo" ) } - </ViewLink> - } + </ButtonStyledLink> + </PositionAndViewLinkWrapper> + </td> + <td className="yoast-table--nopadding"> + <PositionOverTimeButton onClick={ onPositionOverTimeClick }> + <PositionOverTimeCell rowData={ rowData } /> + </PositionOverTimeButton> </td> + <td>{ formatLastUpdated( rowData.updated_at ) }</td> </Fragment> ); } @@ -201,10 +301,14 @@ export default function WincherTableRow( props ) { isFocusKeyphrase, isDisabled, isLoading, + isSelected, + onSelectKeyphrases, } = props; const isEnabled = ! isEmpty( rowData ); + const hasHistory = ! isEmpty( rowData?.position?.history ); + const toggleAction = useCallback( () => { if ( isDisabled ) { @@ -220,14 +324,35 @@ export default function WincherTableRow( props ) { [ keyphrase, onTrackKeyphrase, onUntrackKeyphrase, isEnabled, rowData, isDisabled ] ); - return <tr> - <td className="yoast-table--nopadding"> - { renderToggleState( { keyphrase, isEnabled, toggleAction, isLoading } ) } - </td> - <td>{ keyphrase }{ isFocusKeyphrase && <span>*</span> }</td> + /** + * Fires when checkbox value changes + * + * @returns {void} + */ + const onChange = useCallback( () => { + onSelectKeyphrases( prev => isSelected ? prev.filter( e => e !== keyphrase ) : prev.concat( keyphrase ) ); + }, [ onSelectKeyphrases, isSelected, keyphrase ] ); + + return <WincherTableRowElement isEnabled={ isEnabled }> + <SelectKeyphraseCheckboxWrapper> + { hasHistory && <Checkbox + id={ "select-" + keyphrase } + onChange={ onChange } + checked={ isSelected } + label="" + /> } + </SelectKeyphraseCheckboxWrapper> + + <KeyphraseTdWrapper> + { keyphrase }{ isFocusKeyphrase && <span>*</span> } + </KeyphraseTdWrapper> { getPositionalDataByState( props ) } - </tr>; + + <TrackingTdWrapper> + { renderToggleState( { keyphrase, isEnabled, toggleAction, isLoading } ) } + </TrackingTdWrapper> + </WincherTableRowElement>; } WincherTableRow.propTypes = { @@ -240,6 +365,8 @@ WincherTableRow.propTypes = { isLoading: PropTypes.bool, // eslint-disable-next-line react/no-unused-prop-types websiteId: PropTypes.string, + isSelected: PropTypes.bool.isRequired, + onSelectKeyphrases: PropTypes.func.isRequired, }; WincherTableRow.defaultProps = { diff --git a/packages/js/src/components/fills/MetaboxFill.js b/packages/js/src/components/fills/MetaboxFill.js index fc4a0d405a5..954ea2ff90b 100644 --- a/packages/js/src/components/fills/MetaboxFill.js +++ b/packages/js/src/components/fills/MetaboxFill.js @@ -1,12 +1,12 @@ /* External dependencies */ import { useSelect } from "@wordpress/data"; -import { Fragment, useCallback } from "@wordpress/element"; +import { Fragment } from "@wordpress/element"; import { Fill } from "@wordpress/components"; import { __ } from "@wordpress/i18n"; import PropTypes from "prop-types"; -import { colors } from "@yoast/style-guide"; /* Internal dependencies */ +import WincherSEOPerformanceModal from "../../containers/WincherSEOPerformanceModal"; import CollapsibleCornerstone from "../../containers/CollapsibleCornerstone"; import SnippetEditor from "../../containers/SnippetEditor"; import Warning from "../../containers/Warning"; @@ -19,7 +19,6 @@ import AdvancedSettings from "../../containers/AdvancedSettings"; import SocialMetadataPortal from "../portals/SocialMetadataPortal"; import SchemaTabContainer from "../../containers/SchemaTab"; import SEMrushRelatedKeyphrases from "../../containers/SEMrushRelatedKeyphrases"; -import WincherSEOPerformance from "../../containers/WincherSEOPerformance"; import { isWordProofIntegrationActive } from "../../helpers/wordproof"; import WordProofAuthenticationModals from "../../components/modals/WordProofAuthenticationModals"; import PremiumSEOAnalysisModal from "../modals/PremiumSEOAnalysisModal"; @@ -37,23 +36,10 @@ const BlackFridayPromotionWithMetaboxWarningsCheck = withMetaboxWarningsCheck( B * Creates the Metabox component. * * @param {Object} settings The feature toggles. - * @param {Object} store The Redux store. - * @param {Object} theme The theme to use. - * @param {Array} wincherKeyphrases The Wincher trackable keyphrases. - * @param {Function} setWincherNoKeyphrase Sets wincher no keyphrases in the store. * * @returns {wp.Element} The Metabox component. */ -export default function MetaboxFill( { settings, wincherKeyphrases, setWincherNoKeyphrase } ) { - const onToggleWincher = useCallback( () => { - if ( ! wincherKeyphrases.length ) { - setWincherNoKeyphrase( true ); - // This is fragile, should replace with a real React ref. - document.querySelector( "#focus-keyword-input-metabox" ).focus(); - return false; - } - }, [ wincherKeyphrases, setWincherNoKeyphrase ] ); - +export default function MetaboxFill( { settings } ) { const isTerm = useSelect( ( select ) => select( "yoast-seo/editor" ).getIsTerm(), [] ); const isProduct = useSelect( ( select ) => select( "yoast-seo/editor" ).getIsProduct(), [] ); @@ -117,17 +103,9 @@ export default function MetaboxFill( { settings, wincherKeyphrases, setWincherNo </SidebarItem> } { settings.isKeywordAnalysisActive && settings.isWincherIntegrationActive && <SidebarItem key="wincher-seo-performance" renderPriority={ 25 }> - <MetaboxCollapsible - id={ "yoast-wincher-seo-performance-metabox" } - title={ __( "Track SEO performance", "wordpress-seo" ) } - initialIsOpen={ false } - prefixIcon={ { icon: "chart-square-bar", color: colors.$color_grey_medium_dark } } - prefixIconCollapsed={ { icon: "chart-square-bar", color: colors.$color_grey_medium_dark } } - onToggle={ onToggleWincher } - > - <WincherSEOPerformance /> - </MetaboxCollapsible> - </SidebarItem> } + <WincherSEOPerformanceModal location="metabox" /> + </SidebarItem> + } { settings.isCornerstoneActive && <SidebarItem key="cornerstone" renderPriority={ 30 }> <CollapsibleCornerstone /> </SidebarItem> } @@ -155,8 +133,6 @@ export default function MetaboxFill( { settings, wincherKeyphrases, setWincherNo MetaboxFill.propTypes = { settings: PropTypes.object.isRequired, - wincherKeyphrases: PropTypes.array.isRequired, - setWincherNoKeyphrase: PropTypes.func.isRequired, }; /* eslint-enable complexity */ diff --git a/packages/js/src/components/modals/PremiumSEOAnalysisUpsell.js b/packages/js/src/components/modals/PremiumSEOAnalysisUpsell.js index e677e0fe062..a742c5fcab3 100644 --- a/packages/js/src/components/modals/PremiumSEOAnalysisUpsell.js +++ b/packages/js/src/components/modals/PremiumSEOAnalysisUpsell.js @@ -28,7 +28,7 @@ const PremiumSEOAnalysisUpsell = ( props ) => { return ( <UpsellBox title={ __( "Get more help with writing content that ranks", "wordpress-seo" ) } - description={ __( "Check your text on more SEO criteria and get an enhanced keyphrase analysis, making it easier to write optimized content.", "wordpress-seo" ) } + description={ __( "Check your text on even more SEO criteria and get an enhanced keyphrase analysis, making it easier to optimize your content.", "wordpress-seo" ) } benefitsTitle={ __( "What’s more in Yoast SEO Premium?", "wordpress-seo" ) } benefits={ benefits } upsellButtonText={ diff --git a/packages/js/src/components/modals/WincherUpgradeCallout.js b/packages/js/src/components/modals/WincherUpgradeCallout.js index e74f41b4f24..88a6b4f1a76 100644 --- a/packages/js/src/components/modals/WincherUpgradeCallout.js +++ b/packages/js/src/components/modals/WincherUpgradeCallout.js @@ -56,13 +56,15 @@ const CalloutContainer = styled.div` /** * Hook to fetch the account tracking info. * + * @param {boolean} isLoggedIn Whether the use is logged in. + * * @returns {object} The Wincher account tracking info. */ -const useTrackingInfo = () => { +export const useTrackingInfo = ( isLoggedIn ) => { const [ trackingInfo, setTrackingInfo ] = useState( null ); useEffect( ()=>{ - if ( ! trackingInfo ) { + if ( isLoggedIn && ! trackingInfo ) { checkLimit().then( data => setTrackingInfo( data ) ); } }, [ trackingInfo ] ); @@ -70,6 +72,10 @@ const useTrackingInfo = () => { return trackingInfo; }; +useTrackingInfo.propTypes = { + limit: PropTypes.bool.isRequired, +}; + /** * Hook to fetch the upgrade campaign. * @@ -221,8 +227,7 @@ WincherUpgradeCalloutDescription.propTypes = { * * @returns {wp.Element | null} The Wincher upgrade callout. */ -const WincherUpgradeCallout = ( { onClose, isTitleShortened } ) => { - const trackingInfo = useTrackingInfo(); +const WincherUpgradeCallout = ( { onClose, isTitleShortened, trackingInfo } ) => { const upgradeCampaign = useUpgradeCampaign(); if ( trackingInfo === null ) { @@ -257,6 +262,7 @@ const WincherUpgradeCallout = ( { onClose, isTitleShortened } ) => { WincherUpgradeCallout.propTypes = { onClose: PropTypes.func, isTitleShortened: PropTypes.bool, + trackingInfo: PropTypes.object, }; export default WincherUpgradeCallout; diff --git a/packages/js/src/containers/MetaboxFill.js b/packages/js/src/containers/MetaboxFill.js index 518650b4a7f..7ef90c3056b 100644 --- a/packages/js/src/containers/MetaboxFill.js +++ b/packages/js/src/containers/MetaboxFill.js @@ -1,4 +1,4 @@ -import { withSelect, withDispatch } from "@wordpress/data"; +import { withSelect } from "@wordpress/data"; import { compose } from "@wordpress/compose"; import MetaboxFill from "../components/fills/MetaboxFill"; @@ -6,19 +6,11 @@ export default compose( [ withSelect( ( select, ownProps ) => { const { getPreferences, - getWincherTrackableKeyphrases, } = select( "yoast-seo/editor" ); return { settings: getPreferences(), store: ownProps.store, - wincherKeyphrases: getWincherTrackableKeyphrases(), - }; - } ), - withDispatch( ( dispatch ) => { - const { setWincherNoKeyphrase } = dispatch( "yoast-seo/editor" ); - return { - setWincherNoKeyphrase, }; } ), ] )( MetaboxFill ); diff --git a/packages/js/src/containers/WincherKeyphrasesTable.js b/packages/js/src/containers/WincherKeyphrasesTable.js index d762cdb5456..33ed6d2d6b6 100644 --- a/packages/js/src/containers/WincherKeyphrasesTable.js +++ b/packages/js/src/containers/WincherKeyphrasesTable.js @@ -9,7 +9,6 @@ export default compose( [ withSelect( ( select ) => { const { getWincherWebsiteId, - getWincherTrackedKeyphrases, getWincherTrackableKeyphrases, getWincherLoginStatus, getWincherPermalink, @@ -21,7 +20,6 @@ export default compose( [ return { focusKeyphrase: getFocusKeyphrase(), keyphrases: getWincherTrackableKeyphrases(), - trackedKeyphrases: getWincherTrackedKeyphrases(), isLoggedIn: getWincherLoginStatus(), trackAll: shouldWincherTrackAll(), websiteId: getWincherWebsiteId(), diff --git a/packages/js/src/containers/WincherSEOPerformance.js b/packages/js/src/containers/WincherSEOPerformance.js index 67e0f6e69ad..f94d7826817 100644 --- a/packages/js/src/containers/WincherSEOPerformance.js +++ b/packages/js/src/containers/WincherSEOPerformance.js @@ -11,10 +11,12 @@ export default compose( [ isWincherNewlyAuthenticated, getWincherKeyphraseLimitReached, getWincherLimit, + getWincherHistoryDaysLimit, getWincherLoginStatus, getWincherRequestIsSuccess, getWincherRequestResponse, getWincherTrackableKeyphrases, + getWincherTrackedKeyphrases, getWincherAllKeyphrasesMissRanking, getWincherPermalink, shouldWincherAutomaticallyTrackAll, @@ -22,6 +24,7 @@ export default compose( [ return { keyphrases: getWincherTrackableKeyphrases(), + trackedKeyphrases: getWincherTrackedKeyphrases(), allKeyphrasesMissRanking: getWincherAllKeyphrasesMissRanking(), isLoggedIn: getWincherLoginStatus(), isNewlyAuthenticated: isWincherNewlyAuthenticated(), @@ -31,6 +34,7 @@ export default compose( [ response: getWincherRequestResponse(), shouldTrackAll: shouldWincherAutomaticallyTrackAll(), permalink: getWincherPermalink(), + historyDaysLimit: getWincherHistoryDaysLimit(), }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/js/src/editor-modules.js b/packages/js/src/editor-modules.js index 5b46a7ea784..f2f3eded531 100644 --- a/packages/js/src/editor-modules.js +++ b/packages/js/src/editor-modules.js @@ -6,6 +6,7 @@ import getL10nObject from "./analysis/getL10nObject"; import isZapierConnected from "./analysis/isZapierConnected"; import isZapierIntegrationActive from "./analysis/isZapierIntegrationActive"; import * as refreshAnalysis from "./analysis/refreshAnalysis"; +import KeywordInput from "./components/contentAnalysis/KeywordInputComponent"; import * as mapResults from "./components/contentAnalysis/mapResults"; import HelpLink from "./components/HelpLink"; import withYoastSidebarPriority from "./components/higherorder/withYoastSidebarPriority"; @@ -29,7 +30,7 @@ import * as i18n from "./helpers/i18n"; import isBlockEditor from "./helpers/isBlockEditor"; import * as replacementVariableHelpers from "./helpers/replacementVariableHelpers"; import { update as updateAdminBar } from "./ui/adminBar"; -import { updateScore, createScoresInPublishBox, scrollToCollapsible } from "./ui/publishBox"; +import { createScoresInPublishBox, scrollToCollapsible, updateScore } from "./ui/publishBox"; import { update as updateTrafficLight } from "./ui/trafficLight"; window.yoast = window.yoast || {}; @@ -50,6 +51,7 @@ window.yoast.editorModules = { withYoastSidebarPriority, }, contentAnalysis: { + KeywordInput, mapResults, }, contexts: { diff --git a/packages/js/src/helpers/wincherEndpoints.js b/packages/js/src/helpers/wincherEndpoints.js index f913a7c80f6..b05a87f00c1 100644 --- a/packages/js/src/helpers/wincherEndpoints.js +++ b/packages/js/src/helpers/wincherEndpoints.js @@ -58,18 +58,20 @@ export async function authenticate( responseData ) { * Gets the tracked keyphrases data via POST. * * @param {Array} keyphrases The keyphrases to get the data for. + * @param {string} startAt The keyphrases to get the data for. * @param {String} permalink The post's/page's permalink. Optional. * @param {AbortSignal} signal (optional) Abort signal. * * @returns {Promise} The API response promise. */ -export async function getKeyphrases( keyphrases = null, permalink = null, signal ) { +export async function getKeyphrases( keyphrases = null, startAt = null, permalink = null, signal ) { return await callEndpoint( { path: "yoast/v1/wincher/keyphrases", method: "POST", data: { keyphrases, permalink, + startAt, }, signal, } ); diff --git a/packages/js/src/initializers/admin.js b/packages/js/src/initializers/admin.js index 8aaf8617ae3..2b329b414f5 100644 --- a/packages/js/src/initializers/admin.js +++ b/packages/js/src/initializers/admin.js @@ -313,14 +313,6 @@ export default function initAdmin( jQuery ) { } } ).trigger( "change" ); - // Toggle the Wincher section. - jQuery( "#wincher_integration_active input[type='radio']" ).change( function() { - // The value on is enabled, off is disabled. - if ( jQuery( this ).is( ":checked" ) ) { - jQuery( "#wincher-connection" ).toggle( jQuery( this ).val() === "on" ); - } - } ).change(); - // Handle the settings pages tabs. jQuery( "#wpseo-tabs" ).find( "a" ).on( "click", function( event ) { var canChangeTab = true; diff --git a/packages/js/src/redux/reducers/WincherRequest.js b/packages/js/src/redux/reducers/WincherRequest.js index c8b4622d213..d16c63952b1 100644 --- a/packages/js/src/redux/reducers/WincherRequest.js +++ b/packages/js/src/redux/reducers/WincherRequest.js @@ -19,6 +19,7 @@ const INITIAL_STATE = { limit: 10, trackAll: false, automaticallyTrack: false, + historyDaysLimit: 0, }; /** * A reducer for the Wincher request. diff --git a/packages/js/src/redux/selectors/WincherRequest.js b/packages/js/src/redux/selectors/WincherRequest.js index c802aa84bff..8f064721f8e 100644 --- a/packages/js/src/redux/selectors/WincherRequest.js +++ b/packages/js/src/redux/selectors/WincherRequest.js @@ -66,6 +66,17 @@ export function getWincherLimit( state ) { return state.WincherRequest.limit; } +/** + * Gets the history days limit. + * + * @param {Object} state The state. + * + * @returns {int} The history days limit assigned to the user account. + */ +export function getWincherHistoryDaysLimit( state ) { + return state.WincherRequest.historyDays; +} + /** * Determines whether all keyphrases should be tracked. * diff --git a/packages/js/src/redux/selectors/WincherSEOPerformance.js b/packages/js/src/redux/selectors/WincherSEOPerformance.js index a39eff63566..26cb970788c 100644 --- a/packages/js/src/redux/selectors/WincherSEOPerformance.js +++ b/packages/js/src/redux/selectors/WincherSEOPerformance.js @@ -48,8 +48,7 @@ export function hasWincherTrackedKeyphrases( state ) { export function getWincherTrackableKeyphrases( state ) { const isPremium = getL10nObject().isPremium; const premiumStore = window.wp.data.select( "yoast-seo-premium/editor" ); - const tracked = Object.keys( getWincherTrackedKeyphrases( state ) || {} ).map( k => k.trim() ); - const keyphrases = [ state.focusKeyword.trim(), ...tracked ]; + const keyphrases = [ state.focusKeyword.trim() ]; if ( isPremium && premiumStore ) { // eslint-disable-next-line no-undefined diff --git a/packages/js/src/shared-admin/components/ai-generate-titles-and-descriptions-upsell.js b/packages/js/src/shared-admin/components/ai-generate-titles-and-descriptions-upsell.js index f6a7c74b349..6d8be224bf4 100644 --- a/packages/js/src/shared-admin/components/ai-generate-titles-and-descriptions-upsell.js +++ b/packages/js/src/shared-admin/components/ai-generate-titles-and-descriptions-upsell.js @@ -27,7 +27,7 @@ export const AiGenerateTitlesAndDescriptionsUpsell = ( { learnMoreLink, upsellLi <Badge className="yst-absolute yst-top-0 yst-right-2 yst-mt-2 yst-ml-2" variant="info">Beta</Badge> </div> <div className="yst-mt-6 yst-text-xs yst-font-medium"> - <span className="yst-uppercase yst-text-slate-500"> + <span className="yst-introduction-modal-uppercase"> { sprintf( /* translators: %1$s expands to Yoast SEO Premium. */ __( "New to %1$s", "wordpress-seo" ), @@ -76,7 +76,11 @@ export const AiGenerateTitlesAndDescriptionsUpsell = ( { learnMoreLink, upsellLi ref={ initialFocus } > <LockOpenIcon className="yst--ml-1 yst-mr-2 yst-h-5 yst-w-5" /> - { __( "Unlock with Premium", "wordpress-seo" ) } + { sprintf( + /* translators: %1$s expands to Yoast SEO Premium. */ + __( "Unlock with %1$s", "wordpress-seo" ), + "Yoast SEO Premium" + ) } <span className="yst-sr-only"> { /* translators: Hidden accessibility text. */ diff --git a/packages/js/tests/components/WincherKeyphrasesTable.test.js b/packages/js/tests/components/WincherKeyphrasesTable.test.js index 24c6418a16f..ac0d3f8cc77 100644 --- a/packages/js/tests/components/WincherKeyphrasesTable.test.js +++ b/packages/js/tests/components/WincherKeyphrasesTable.test.js @@ -58,6 +58,8 @@ describe( "WincherKeyphrasesTable", () => { removeTrackedKeyphrase={ noop } setHasTrackedAll={ noop } permalink="" + selectedKeyphrases={ [] } + onSelectKeyphrases={ noop } /> ); expect( component.find( "tbody" ).getElement().props.children.length ).toEqual( 1 ); @@ -78,6 +80,8 @@ describe( "WincherKeyphrasesTable", () => { removeTrackedKeyphrase={ noop } setHasTrackedAll={ noop } permalink="" + selectedKeyphrases={ [] } + onSelectKeyphrases={ noop } /> ); const rows = component.find( WincherTableRow ); @@ -103,6 +107,8 @@ describe( "WincherKeyphrasesTable", () => { isLoggedIn={ true } trackAll={ true } permalink="" + selectedKeyphrases={ [] } + onSelectKeyphrases={ noop } /> ); } ); @@ -125,6 +131,8 @@ describe( "WincherKeyphrasesTable", () => { setHasTrackedAll={ noop } permalink="" focusKeyphrase={ "Yoast SEO" } + selectedKeyphrases={ [] } + onSelectKeyphrases={ noop } /> ); const rows = component.find( WincherTableRow ); diff --git a/packages/js/tests/components/WincherTableRow.test.js b/packages/js/tests/components/WincherTableRow.test.js index c3ee6dee972..c92111d11a2 100644 --- a/packages/js/tests/components/WincherTableRow.test.js +++ b/packages/js/tests/components/WincherTableRow.test.js @@ -1,8 +1,17 @@ import { shallow } from "enzyme"; -import WincherTableRow, { PositionOverTimeChart } from "../../src/components/WincherTableRow"; +import WincherTableRow, { + PositionOverTimeChart, + PositionOverTimeCell, + CaretIcon, + PositionChangeValue, + SelectKeyphraseCheckboxWrapper, + KeyphraseTdWrapper, + TrackingTdWrapper, +} from "../../src/components/WincherTableRow"; import { Toggle } from "@yoast/components"; import WincherSEOPerformanceLoading from "../../src/components/modals/WincherSEOPerformanceLoading"; +import { noop } from "lodash"; const keyphrasesData = { "yoast seo": { @@ -20,6 +29,7 @@ const keyphrasesData = { value: 38, }, ], + change: -2, }, // eslint-disable-next-line camelcase updated_at: new Date(), @@ -38,45 +48,128 @@ describe( "WincherTableRow", () => { const component = shallow( <WincherTableRow rowData={ keyphrasesData[ "woocommerce seo" ] } keyphrase="woocommerce seo" + isSelected={ false } + onSelectKeyphrases={ noop } /> ); - expect( component.find( "td" ).length ).toEqual( 3 ); - expect( component.find( "td" ).at( 1 ).text() ).toEqual( "woocommerce seo" ); - expect( component.find( "td" ).at( 2 ).getElement().props.children ).toEqual( <WincherSEOPerformanceLoading /> ); + expect( component.find( "td" ).length ).toEqual( 1 ); + expect( component.find( "td" ).at( 0 ).getElement().props.children ).toEqual( <WincherSEOPerformanceLoading /> ); + expect( component.find( SelectKeyphraseCheckboxWrapper ).length ).toEqual( 1 ); + expect( component.find( KeyphraseTdWrapper ).length ).toEqual( 1 ); + expect( component.find( KeyphraseTdWrapper ).at( 0 ).text() ).toEqual( "woocommerce seo" ); + expect( component.find( TrackingTdWrapper ).length ).toEqual( 1 ); } ); it( "should render a row with the available data and with chart data", () => { const component = shallow( <WincherTableRow rowData={ keyphrasesData[ "yoast seo" ] } keyphrase="yoast seo" + isSelected={ false } + onSelectKeyphrases={ noop } /> ); - expect( component.find( "td" ).length ).toEqual( 5 ); + expect( component.find( "td" ).length ).toEqual( 3 ); expect( component.find( Toggle ).length ).toEqual( 1 ); - expect( component.find( PositionOverTimeChart ).length ).toEqual( 1 ); + expect( component.find( PositionOverTimeCell ).length ).toEqual( 1 ); + expect( component.find( SelectKeyphraseCheckboxWrapper ).length ).toEqual( 1 ); + expect( component.find( KeyphraseTdWrapper ).length ).toEqual( 1 ); + expect( component.find( TrackingTdWrapper ).length ).toEqual( 1 ); expect( component.find( Toggle ).getElement().props.id ).toBe( "toggle-keyphrase-tracking-yoast seo" ); expect( component.find( Toggle ).getElement().props.isEnabled ).toBe( true ); expect( component.find( Toggle ).getElement().props.showToggleStateLabel ).toBe( false ); - expect( component.find( "td" ).at( 1 ).text() ).toEqual( "yoast seo" ); - expect( component.find( "td" ).at( 2 ).text() ).toEqual( "10" ); + expect( component.find( KeyphraseTdWrapper ).at( 0 ).text() ).toEqual( "yoast seo" ); + expect( component.find( "td" ).at( 0 ).text() ).toContain( "10" ); + expect( component.find( "td" ).at( 2 ).text() ).toEqual( "a few seconds ago" ); } ); it( "should not render an enabled toggle or any position and chart data when no data is available", () => { const component = shallow( <WincherTableRow rowData={ {} } keyphrase="yoast seo" + isSelected={ false } + onSelectKeyphrases={ noop } /> ); - expect( component.find( "td" ).length ).toEqual( 5 ); + expect( component.find( "td" ).length ).toEqual( 1 ); expect( component.find( Toggle ).length ).toEqual( 1 ); - expect( component.find( PositionOverTimeChart ).length ).toEqual( 0 ); + expect( component.find( PositionOverTimeCell ).length ).toEqual( 0 ); + expect( component.find( TrackingTdWrapper ).length ).toEqual( 1 ); expect( component.find( Toggle ).getElement().props.id ).toBe( "toggle-keyphrase-tracking-yoast seo" ); expect( component.find( Toggle ).getElement().props.isEnabled ).toBe( false ); - expect( component.find( "td" ).at( 1 ).text() ).toEqual( "yoast seo" ); - expect( component.find( "td" ).at( 2 ).text() ).toEqual( "?" ); - expect( component.find( "td" ).at( 3 ).text() ).toEqual( "?" ); + expect( component.find( KeyphraseTdWrapper ).at( 0 ).text() ).toEqual( "yoast seo" ); + expect( component.find( "td" ).at( 0 ).text() ).toEqual( "Activate tracking to show the ranking position" ); + } ); +} ); + + +describe( "PositionOverTimeCell", () => { + it( "should render chart but not change if undefined position change", () => { + const component = shallow( <PositionOverTimeCell + rowData={ { + position: { + value: 10, + history: [], + }, + } } + /> ); + + expect( component.find( PositionOverTimeChart ).length ).toEqual( 1 ); + expect( component.find( CaretIcon ).length ).toEqual( 0 ); + expect( component.find( PositionChangeValue ).length ).toEqual( 0 ); + } ); + + it( "should render chart but not change if no position change", () => { + const component = shallow( <PositionOverTimeCell + rowData={ { + position: { + value: 10, + history: [], + change: 0, + }, + } } + /> ); + + expect( component.find( PositionOverTimeChart ).length ).toEqual( 1 ); + expect( component.find( CaretIcon ).length ).toEqual( 0 ); + expect( component.find( PositionChangeValue ).length ).toEqual( 0 ); + } ); + + it( "should render chart and improving position change", () => { + const component = shallow( <PositionOverTimeCell + rowData={ { + position: { + value: 10, + history: [], + // improving + change: -2, + }, + } } + /> ); + + expect( component.find( PositionOverTimeChart ).length ).toEqual( 1 ); + expect( component.find( CaretIcon ).getElement().props.isImproving ).toEqual( true ); + expect( component.find( PositionChangeValue ).getElement().props.isImproving ).toEqual( true ); + expect( component.find( PositionChangeValue ).text() ).toEqual( "2" ); + } ); + + it( "should render chart and declined position change", () => { + const component = shallow( <PositionOverTimeCell + rowData={ { + position: { + value: 10, + history: [], + // declined + change: 2, + }, + } } + /> ); + + expect( component.find( PositionOverTimeChart ).length ).toEqual( 1 ); + expect( component.find( CaretIcon ).getElement().props.isImproving ).toEqual( false ); + expect( component.find( PositionChangeValue ).getElement().props.isImproving ).toEqual( false ); + expect( component.find( PositionChangeValue ).text() ).toEqual( "2" ); } ); } ); diff --git a/readme.txt b/readme.txt index e9f251db44c..b3aa22aad99 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://yoa.st/1up License: GPLv3 License URI: http://www.gnu.org/licenses/gpl.html Tags: SEO, XML sitemap, Content analysis, Readability, Schema -Tested up to: 6.3 +Tested up to: 6.4 Stable tag: 21.4 Requires PHP: 7.2.5 @@ -350,7 +350,7 @@ Release date: 2023-10-31 * Improves the _inclusive language_ analysis by making the feedback more clear and consistent, refining the list of targeted phrases, and adding more alternatives for some of the non-inclusive phrases. Specifically, this includes the following changes: * Aligns the traffic light color and written feedback for all phrases. * Makes some feedback strings more accurate by replacing the word ‘overgeneralizing’ with ‘harmful’. - * Adds 'Rom' and ‘Roma’ as additional alternatives to ‘gypsy’. + * Adds ‘Rom’ and ‘Roma’ as additional alternatives to ‘gypsy’. * Adds additional alternatives to ‘homosexuals’. * Improves the feedback shown for the phrases ‘abnormal behaviour’, ‘behaviourally normal’, and ‘behaviourally abnormal’. * Improves the feedback shown for the word ‘minorities’. @@ -360,14 +360,15 @@ Release date: 2023-10-31 #### Bugfixes -* Specifies the correct prop type for props that can contain an interpolated string. +* Fixes a bug where console warnings about incorrect prop types would be shown on the integrations page. #### Other * Adds indexation exclusion for Gutenberg Patterns taxonomy. * Improves the FAQ block description by removing any reference to the previous restriction of one block per post. -* Improves the inline documentation for the `WPSEO_Option` class. Props to @costdev. +* Improves the inline documentation for the `WPSEO_Option` class. Props to [costdev](https://github.com/costdev). * Leverages Script Strategy feature to add the async attribute to the `wordproof` script in case WordPress version is 6.3 or higher. Props to [adamsilverstein](https://github.com/adamsilverstein). +* Sets the WordPress tested up to version to 6.4. = 21.4 = diff --git a/src/actions/wincher/wincher-account-action.php b/src/actions/wincher/wincher-account-action.php index ea9bb54cfd8..fefcc80bd33 100644 --- a/src/actions/wincher/wincher-account-action.php +++ b/src/actions/wincher/wincher-account-action.php @@ -48,14 +48,16 @@ public function check_limit() { try { $results = $this->client->get( self::ACCOUNT_URL ); - $usage = $results['limits']['keywords']['usage']; - $limit = $results['limits']['keywords']['limit']; + $usage = $results['limits']['keywords']['usage']; + $limit = $results['limits']['keywords']['limit']; + $history = $results['limits']['history_days']; return (object) [ - 'canTrack' => \is_null( $limit ) || $usage < $limit, - 'limit' => $limit, - 'usage' => $usage, - 'status' => 200, + 'canTrack' => \is_null( $limit ) || $usage < $limit, + 'limit' => $limit, + 'usage' => $usage, + 'historyDays' => $history, + 'status' => 200, ]; } catch ( \Exception $e ) { return (object) [ @@ -72,13 +74,13 @@ public function check_limit() { */ public function get_upgrade_campaign() { try { - $result = $this->client->get( self::UPGRADE_CAMPAIGN_URL ); - $type = $result['type']; - $months = $result['months']; + $result = $this->client->get( self::UPGRADE_CAMPAIGN_URL ); + $type = isset( $result['type'] ) ? $result['type'] : null; + $months = isset( $result['months'] ) ? $result['months'] : null; + $discount = isset( $result['value'] ) ? $result['value'] : null; - // We display upgrade discount only if it's a rate discount and positive months. - if ( $type === 'RATE' && $months && $months > 0 ) { - $discount = $result['value']; + // We display upgrade discount only if it's a rate discount and positive months/discount. + if ( $type === 'RATE' && $months && $discount ) { return (object) [ 'discount' => $discount, diff --git a/src/actions/wincher/wincher-keyphrases-action.php b/src/actions/wincher/wincher-keyphrases-action.php index 28a5aa0b8a4..1de4f97e741 100644 --- a/src/actions/wincher/wincher-keyphrases-action.php +++ b/src/actions/wincher/wincher-keyphrases-action.php @@ -182,10 +182,11 @@ public function untrack_keyphrase( $keyphrase_id ) { * * @param array|null $used_keyphrases The currently used keyphrases. Optional. * @param string|null $permalink The current permalink. Optional. + * @param string|null $start_at The position start date. Optional. * * @return object The keyphrase chart data. */ - public function get_tracked_keyphrases( $used_keyphrases = null, $permalink = null ) { + public function get_tracked_keyphrases( $used_keyphrases = null, $permalink = null, $start_at = null ) { try { if ( $used_keyphrases === null ) { $used_keyphrases = $this->collect_all_keyphrases(); @@ -213,6 +214,7 @@ public function get_tracked_keyphrases( $used_keyphrases = null, $permalink = nu [ 'keywords' => $used_keyphrases, 'url' => $permalink, + 'start_at' => $start_at, ] ), [ diff --git a/src/integrations/third-party/wincher.php b/src/deprecated/src/integrations/third-party/wincher.php similarity index 53% rename from src/integrations/third-party/wincher.php rename to src/deprecated/src/integrations/third-party/wincher.php index 6ce08e9f323..a9f09d493a1 100644 --- a/src/integrations/third-party/wincher.php +++ b/src/deprecated/src/integrations/third-party/wincher.php @@ -9,6 +9,9 @@ /** * Adds the Wincher integration. + * + * @deprecated 21.6 + * @codeCoverageIgnore */ class Wincher implements Integration_Interface { @@ -22,6 +25,9 @@ class Wincher implements Integration_Interface { /** * The Wincher integration toggle constructor. * + * @deprecated 21.6 + * @codeCoverageIgnore + * * @param Wincher_Helper $wincher The Wincher helper instance. */ public function __construct( Wincher_Helper $wincher ) { @@ -31,23 +37,21 @@ public function __construct( Wincher_Helper $wincher ) { /** * Initializes the integration. * + * @deprecated 21.6 + * @codeCoverageIgnore + * * @return void */ public function register_hooks() { - /** - * Called by Yoast_Integration_Toggles to add extra toggles to the ones defined there. - */ - \add_filter( 'wpseo_integration_toggles', [ $this, 'add_integration_toggle' ] ); - - /** - * Add extra text after the network integration toggle if the toggle is disabled. - */ - \add_action( 'Yoast\WP\SEO\admin_network_integration_after', [ $this, 'after_network_integration_toggle' ] ); + \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); } /** * Returns the conditionals based in which this loadable should be active. * + * @deprecated 21.6 + * @codeCoverageIgnore + * * @return array The conditionals. */ public static function get_conditionals() { @@ -57,25 +61,15 @@ public static function get_conditionals() { /** * Adds the Wincher integration toggle to the $integration_toggles array. * + * @deprecated 21.6 + * @codeCoverageIgnore + * * @param array $integration_toggles The integration toggles array. * * @return array The updated integration toggles array. */ public function add_integration_toggle( $integration_toggles ) { - if ( \is_array( $integration_toggles ) ) { - $integration_toggles[] = (object) [ - /* translators: %s: 'Wincher' */ - 'name' => \sprintf( \__( '%s integration', 'wordpress-seo' ), 'Wincher' ), - 'setting' => 'wincher_integration_active', - 'label' => \sprintf( - /* translators: %s: 'Wincher' */ - \__( 'The %s integration offers the option to track specific keyphrases and gain insights in their positions.', 'wordpress-seo' ), - 'Wincher' - ), - 'order' => 11, - 'disabled' => \is_multisite(), - ]; - } + \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); return $integration_toggles; } @@ -83,43 +77,24 @@ public function add_integration_toggle( $integration_toggles ) { /** * Adds the disabled note when the integration toggle is disabled. * - * @deprecated 20.3 + * @deprecated 21.6 * @codeCoverageIgnore * * @param Yoast_Feature_Toggle $integration The integration toggle class. */ public function after_integration_toggle( $integration ) { - \_deprecated_function( __METHOD__, 'Yoast SEO 20.3' ); - if ( $integration->setting === 'wincher_integration_active' ) { - if ( \is_multisite() ) { - $this->get_disabled_note(); - } - } + \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); } /** * Adds the disabled note to the network integration toggle. * - * @param Yoast_Feature_Toggle $integration The integration toggle class. - */ - public function after_network_integration_toggle( $integration ) { - if ( $integration->setting === 'wincher_integration_active' ) { - $this->get_disabled_note(); - } - } - - /** - * Outputs the disabled note. - * + * @deprecated 21.6 * @codeCoverageIgnore * - * @return void + * @param Yoast_Feature_Toggle $integration The integration toggle class. */ - protected function get_disabled_note() { - echo '<p>', \sprintf( - /* translators: %s expands to Wincher */ - \esc_html__( 'Currently, the %s integration is not available for multisites.', 'wordpress-seo' ), - 'Wincher' - ), '</p>'; + public function after_network_integration_toggle( $integration ) { + \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); } } diff --git a/src/integrations/third-party/wordproof-integration-toggle.php b/src/deprecated/src/integrations/third-party/wordproof-integration-toggle.php similarity index 63% rename from src/integrations/third-party/wordproof-integration-toggle.php rename to src/deprecated/src/integrations/third-party/wordproof-integration-toggle.php index d352e7f9045..7c8074f5cb9 100644 --- a/src/integrations/third-party/wordproof-integration-toggle.php +++ b/src/deprecated/src/integrations/third-party/wordproof-integration-toggle.php @@ -10,6 +10,9 @@ /** * Class WordProofIntegrationToggle. * + * @deprecated 21.6 + * @codeCoverageIgnore + * * @package Yoast\WP\SEO\Integrations\Third_Party */ class Wordproof_Integration_Toggle implements Integration_Interface { @@ -24,6 +27,9 @@ class Wordproof_Integration_Toggle implements Integration_Interface { /** * The WordProof integration toggle constructor. * + * @deprecated 21.6 + * @codeCoverageIgnore + * * @param Wordproof_Helper $wordproof The WordProof helper instance. */ public function __construct( Wordproof_Helper $wordproof ) { @@ -33,6 +39,9 @@ public function __construct( Wordproof_Helper $wordproof ) { /** * Returns the conditionals based in which this loadable should be active. * + * @deprecated 21.6 + * @codeCoverageIgnore + * * @return array */ public static function get_conditionals() { @@ -44,51 +53,27 @@ public static function get_conditionals() { * * This is the place to register hooks and filters. * + * @deprecated 21.6 + * @codeCoverageIgnore + * * @return void */ public function register_hooks() { - /** - * Called by Yoast_Integration_Toggles to add extra toggles to the ones defined there. - */ - \add_filter( 'wpseo_integration_toggles', [ $this, 'add_integration_toggle' ] ); - - /** - * Update the default wordproof_integration_active depending if the integration is disabled or not. - */ - \add_filter( 'wpseo_option_wpseo_defaults', [ $this, 'default_values' ] ); - - /** - * Add extra text after the network integration toggle if the toggle is disabled. - */ - \add_action( 'Yoast\WP\SEO\admin_network_integration_after', [ $this, 'after_network_integration_toggle' ] ); + \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); } /** * Adds the WordProof integration toggle to the array. * + * @deprecated 21.6 + * @codeCoverageIgnore + * * @param array $integration_toggles The integration toggles array. * * @return array The updated integration toggles array. */ public function add_integration_toggle( $integration_toggles ) { - if ( \is_array( $integration_toggles ) ) { - $integration_toggles[] = (object) [ - /* translators: %s expands to WordProof */ - 'name' => \sprintf( \__( '%s integration', 'wordpress-seo' ), 'WordProof' ), - 'setting' => 'wordproof_integration_active', - 'label' => \sprintf( - /* translators: %s expands to WordProof */ - \__( '%1$s can be used to timestamp your privacy page.', 'wordpress-seo' ), - 'WordProof' - ), - /* translators: %s expands to WordProof */ - 'read_more_label' => \sprintf( \__( 'Read more about how %s works.', 'wordpress-seo' ), 'WordProof ' ), - 'read_more_url' => 'https://yoa.st/wordproof-integration', - 'order' => 16, - 'disabled' => $this->wordproof->integration_is_disabled(), - 'new' => true, - ]; - } + \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); return $integration_toggles; } @@ -144,14 +129,6 @@ public function after_integration_toggle( $integration ) { * @param Yoast_Feature_Toggle $integration The integration toggle class. */ public function after_network_integration_toggle( $integration ) { - if ( $integration->setting === 'wordproof_integration_active' ) { - if ( $integration->disabled ) { - echo '<p>' . \sprintf( - /* translators: %s expands to WordProof */ - \esc_html__( 'Currently, the %s integration is not available for multisites.', 'wordpress-seo' ), - 'WordProof' - ) . '</p>'; - } - } + \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); } } diff --git a/src/helpers/current-page-helper.php b/src/helpers/current-page-helper.php index 6109a77b932..28f6289925b 100644 --- a/src/helpers/current-page-helper.php +++ b/src/helpers/current-page-helper.php @@ -482,10 +482,12 @@ protected function get_non_cached_date_archive_permalink() { * @return int The amoumt of queried terms. */ protected function count_queried_terms() { - $wp_query = $this->wp_query_wrapper->get_main_query(); - $term = $wp_query->get_queried_object(); + $wp_query = $this->wp_query_wrapper->get_main_query(); + $term = $wp_query->get_queried_object(); + + $queried_terms = $wp_query->tax_query->queried_terms; - if ( empty( $queried_terms[ $term->taxonomy ]['terms'] ) ) { + if ( is_null( $term ) || empty( $queried_terms[ $term->taxonomy ]['terms'] ) ) { return 0; } diff --git a/src/introductions/application/ai-generate-titles-and-descriptions-introduction-upsell.php b/src/introductions/application/ai-generate-titles-and-descriptions-introduction-upsell.php index fd99ebc96a2..14932c2b9c1 100644 --- a/src/introductions/application/ai-generate-titles-and-descriptions-introduction-upsell.php +++ b/src/introductions/application/ai-generate-titles-and-descriptions-introduction-upsell.php @@ -10,8 +10,6 @@ * Represents the introduction for the AI generate titles and introduction upsell. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded - * - * @makePublic */ class Ai_Generate_Titles_And_Descriptions_Introduction_Upsell implements Introduction_Interface { diff --git a/src/introductions/application/introductions-collector.php b/src/introductions/application/introductions-collector.php index d0d4fd5efb4..a1361efac5a 100644 --- a/src/introductions/application/introductions-collector.php +++ b/src/introductions/application/introductions-collector.php @@ -8,8 +8,6 @@ /** * Manages the collection of introductions. - * - * @makePublic */ class Introductions_Collector { diff --git a/src/introductions/user-interface/wistia-embed-permission-route.php b/src/introductions/user-interface/wistia-embed-permission-route.php index 40ccfce0634..6ada268deea 100644 --- a/src/introductions/user-interface/wistia-embed-permission-route.php +++ b/src/introductions/user-interface/wistia-embed-permission-route.php @@ -14,8 +14,6 @@ /** * Registers a route to offer get/set of the wistia embed permission for a user. - * - * @makePublic */ class Wistia_Embed_Permission_Route implements Route_Interface { diff --git a/src/presentations/indexable-term-archive-presentation.php b/src/presentations/indexable-term-archive-presentation.php index cd60880192f..460f83693d6 100644 --- a/src/presentations/indexable-term-archive-presentation.php +++ b/src/presentations/indexable-term-archive-presentation.php @@ -92,7 +92,7 @@ public function generate_meta_description() { * @return array The source. */ public function generate_source() { - if ( ! empty( $this->model->object_id ) ) { + if ( ! empty( $this->model->object_id ) || is_null( \get_queried_object() ) ) { return \get_term( $this->model->object_id, $this->model->object_sub_type ); } @@ -152,7 +152,7 @@ public function generate_robots() { * First we get the no index option for this taxonomy, because it can be overwritten the indexable value for * this specific term. */ - if ( ! $this->taxonomy->is_indexable( $this->source->taxonomy ) ) { + if ( is_wp_error( $this->source ) || ! $this->taxonomy->is_indexable( $this->source->taxonomy ) ) { $robots['index'] = 'noindex'; } @@ -176,6 +176,10 @@ public function generate_title() { return $this->model->title; } + if ( is_wp_error( $this->source ) ) { + return $this->model->title; + } + // Get the SEO title as entered in Search Appearance. $title = $this->options->get( 'title-tax-' . $this->source->taxonomy ); if ( $title ) { @@ -209,6 +213,10 @@ protected function is_multiple_terms_query() { return false; } + if ( is_wp_error( $this->source ) ) { + return false; + } + $queried_terms = $query->tax_query->queried_terms; if ( empty( $queried_terms[ $this->source->taxonomy ]['terms'] ) ) { diff --git a/src/promotions/application/promotion-manager-interface.php b/src/promotions/application/promotion-manager-interface.php index 0b244c9e7bb..a2ef7a3b5dc 100644 --- a/src/promotions/application/promotion-manager-interface.php +++ b/src/promotions/application/promotion-manager-interface.php @@ -4,8 +4,6 @@ /** * Interface for the promotion manager. - * - * @makePublic */ interface Promotion_Manager_Interface { diff --git a/src/promotions/domain/black-friday-checklist-promotion.php b/src/promotions/domain/black-friday-checklist-promotion.php index 8ef4e6dd661..55a4c0be44b 100644 --- a/src/promotions/domain/black-friday-checklist-promotion.php +++ b/src/promotions/domain/black-friday-checklist-promotion.php @@ -4,8 +4,6 @@ /** * Class to manage the Black Friday checklist promotion. - * - * @makePublic */ class Black_Friday_Checklist_Promotion extends Abstract_Promotion implements Promotion_Interface { diff --git a/src/promotions/domain/black-friday-promotion.php b/src/promotions/domain/black-friday-promotion.php index 1693d315350..4fabea28650 100644 --- a/src/promotions/domain/black-friday-promotion.php +++ b/src/promotions/domain/black-friday-promotion.php @@ -4,8 +4,6 @@ /** * Class to manage the Black Friday promotion. - * - * @makePublic */ class Black_Friday_Promotion extends Abstract_Promotion implements Promotion_Interface { diff --git a/src/routes/wincher-route.php b/src/routes/wincher-route.php index 9c1c0126031..85c3ad72ccd 100644 --- a/src/routes/wincher-route.php +++ b/src/routes/wincher-route.php @@ -173,6 +173,9 @@ public function register_routes() { 'permalink' => [ 'required' => false, ], + 'startAt' => [ + 'required' => false, + ], ], ]; @@ -256,7 +259,7 @@ public function track_keyphrases( WP_REST_Request $request ) { * @return WP_REST_Response The response. */ public function get_tracked_keyphrases( WP_REST_Request $request ) { - $data = $this->keyphrases_action->get_tracked_keyphrases( $request['keyphrases'], $request['permalink'] ); + $data = $this->keyphrases_action->get_tracked_keyphrases( $request['keyphrases'], $request['permalink'], $request['startAt'] ); return new WP_REST_Response( $data, $data->status ); } diff --git a/src/user-profiles-additions/user-interface/user-profiles-additions-ui.php b/src/user-profiles-additions/user-interface/user-profiles-additions-ui.php index e58d64ca8e8..4c9c9789ea9 100644 --- a/src/user-profiles-additions/user-interface/user-profiles-additions-ui.php +++ b/src/user-profiles-additions/user-interface/user-profiles-additions-ui.php @@ -2,6 +2,8 @@ namespace Yoast\WP\SEO\User_Profiles_Additions\User_Interface; +use WPSEO_Admin_Asset_Manager; +use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Conditionals\User_Profile_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; @@ -10,6 +12,34 @@ */ class User_Profiles_Additions_Ui implements Integration_Interface { + /** + * Holds the Product_Helper. + * + * @var Product_Helper + */ + private $product_helper; + + /** + * Holds the WPSEO_Admin_Asset_Manager. + * + * @var WPSEO_Admin_Asset_Manager + */ + private $asset_manager; + + /** + * Constructs Academy_Integration. + * + * @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO_Admin_Asset_Manager. + * @param Product_Helper $product_helper The Product_Helper. + */ + public function __construct( + WPSEO_Admin_Asset_Manager $asset_manager, + Product_Helper $product_helper + ) { + $this->asset_manager = $asset_manager; + $this->product_helper = $product_helper; + } + /** * Returns the conditionals based in which this loadable should be active. * @@ -31,13 +61,24 @@ public function register_hooks() { \add_action( 'edit_user_profile', [ $this, 'add_hook_to_user_profile' ] ); } + /** + * Enqueues the assets needed for this integration. + * + * @return void + */ + public function enqueue_assets() { + if ( $this->product_helper->is_premium() ) { + $this->asset_manager->enqueue_style( 'introductions' ); + } + } + /** * Add the inputs needed for SEO values to the User Profile page. * * @param WP_User $user User instance to output for. */ public function add_hook_to_user_profile( $user ) { - + $this->enqueue_assets(); echo '<div class="yoast yoast-settings">'; /** diff --git a/tests/unit/actions/wincher/wincher-account-action-test.php b/tests/unit/actions/wincher/wincher-account-action-test.php index 7c6855f404f..bb6c82eed96 100644 --- a/tests/unit/actions/wincher/wincher-account-action-test.php +++ b/tests/unit/actions/wincher/wincher-account-action-test.php @@ -81,10 +81,11 @@ public function test_check_limit() { ->andReturn( [ 'limits' => [ - 'keywords' => [ + 'keywords' => [ 'usage' => 10, 'limit' => 100, ], + 'history_days' => 31, ], 'status' => 200, ] @@ -92,10 +93,11 @@ public function test_check_limit() { $this->assertEquals( (object) [ - 'canTrack' => true, - 'limit' => 100, - 'usage' => 10, - 'status' => 200, + 'canTrack' => true, + 'limit' => 100, + 'usage' => 10, + 'status' => 200, + 'historyDays' => 31, ], $this->instance->check_limit() ); @@ -113,10 +115,11 @@ public function test_invalid_check_limit() { ->andReturn( [ 'limits' => [ - 'keywords' => [ + 'keywords' => [ 'usage' => 100, 'limit' => 100, ], + 'history_days' => 31, ], 'status' => 200, ] @@ -124,10 +127,11 @@ public function test_invalid_check_limit() { $this->assertEquals( (object) [ - 'canTrack' => false, - 'limit' => 100, - 'usage' => 100, - 'status' => 200, + 'canTrack' => false, + 'limit' => 100, + 'usage' => 100, + 'status' => 200, + 'historyDays' => 31, ], $this->instance->check_limit() ); @@ -145,10 +149,11 @@ public function test_unlimited_check_limit() { ->andReturn( [ 'limits' => [ - 'keywords' => [ + 'keywords' => [ 'usage' => 100000, 'limit' => null, ], + 'history_days' => 31, ], 'status' => 200, ] @@ -156,10 +161,11 @@ public function test_unlimited_check_limit() { $this->assertEquals( (object) [ - 'canTrack' => true, - 'limit' => null, - 'usage' => 100000, - 'status' => 200, + 'canTrack' => true, + 'limit' => null, + 'usage' => 100000, + 'status' => 200, + 'historyDays' => 31, ], $this->instance->check_limit() ); @@ -248,4 +254,25 @@ public function test_valid_get_upgrade_campaign() { $this->instance->get_upgrade_campaign() ); } + + /** + * Tests empty get upgrade campaign. + * + * @covers ::get_upgrade_campaign + */ + public function test_empty_get_upgrade_campaign() { + $this->client_instance + ->expects( 'get' ) + ->with( 'https://api.wincher.com/v1/yoast/upgrade-campaign' ) + ->andReturn( [] ); + + $this->assertEquals( + (object) [ + 'discount' => null, + 'months' => null, + 'status' => 200, + ], + $this->instance->get_upgrade_campaign() + ); + } } diff --git a/tests/unit/actions/wincher/wincher-keyphrases-action-test.php b/tests/unit/actions/wincher/wincher-keyphrases-action-test.php index 03ddc04fcca..812140c2013 100644 --- a/tests/unit/actions/wincher/wincher-keyphrases-action-test.php +++ b/tests/unit/actions/wincher/wincher-keyphrases-action-test.php @@ -264,6 +264,7 @@ public function test_get_tracked_keyphrases() { [ 'keywords' => [ 'yoast seo', 'wincher' ], 'url' => null, + 'start_at' => null, ] ), [ @@ -324,6 +325,7 @@ public function test_get_tracked_keyphrases_no_data_key() { [ 'keywords' => [ 'yoast seo' ], 'url' => null, + 'start_at' => null, ] ), [ @@ -384,6 +386,7 @@ public function test_get_tracked_keyphrases_filtered_by_used_keyphrases() { [ 'keywords' => [ 'yoast seo' ], 'url' => null, + 'start_at' => null, ] ), [ @@ -440,6 +443,7 @@ public function test_get_tracked_keyphrases_with_permalink() { [ 'keywords' => [ 'yoast seo', 'blog seo' ], 'url' => 'https://yoast.com/blog/', + 'start_at' => null, ] ), [ diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php index 4538a077a9d..4fc6bfb17b0 100644 --- a/tests/unit/bootstrap.php +++ b/tests/unit/bootstrap.php @@ -36,7 +36,7 @@ define( 'YOAST_VENDOR_PREFIX_DIRECTORY', 'vendor_prefixed' ); define( 'YOAST_SEO_PHP_REQUIRED', '7.2.5' ); -define( 'YOAST_SEO_WP_TESTED', '6.3.2' ); +define( 'YOAST_SEO_WP_TESTED', '6.4' ); define( 'YOAST_SEO_WP_REQUIRED', '6.2' ); if ( ! defined( 'WPSEO_NAMESPACES' ) ) { diff --git a/tests/unit/integrations/third-party/wincher-test.php b/tests/unit/integrations/third-party/wincher-test.php deleted file mode 100644 index 0d2308f2ac7..00000000000 --- a/tests/unit/integrations/third-party/wincher-test.php +++ /dev/null @@ -1,168 +0,0 @@ -<?php - -namespace Yoast\WP\SEO\Tests\Unit\Integrations\Third_Party; - -use Brain\Monkey; -use Mockery; -use Yoast\WP\SEO\Conditionals\Admin_Conditional; -use Yoast\WP\SEO\Helpers\Wincher_Helper; -use Yoast\WP\SEO\Integrations\Third_Party\Wincher; -use Yoast\WP\SEO\Tests\Unit\TestCase; - -/** - * Class Wincher. - * - * @coversDefaultClass \Yoast\WP\SEO\Integrations\Third_Party\Wincher - * - * @group integrations - */ -class Wincher_Test extends TestCase { - - /** - * The test instance. - * - * @var Wincher|Mockery\MockInterface - */ - private $instance; - - /** - * The Wincher helper instance. - * - * @var Wincher_Helper|Mockery\Mock - */ - protected $wincher; - - /** - * Sets an instance for test purposes. - */ - protected function set_up() { - parent::set_up(); - - $this->stubEscapeFunctions(); - $this->stubTranslationFunctions(); - - $this->wincher = Mockery::mock( Wincher_Helper::class ); - $this->instance = Mockery::mock( Wincher::class, [ $this->wincher ] ) - ->makePartial() - ->shouldAllowMockingProtectedMethods(); - } - - /** - * Tests if given dependencies are set as expected. - * - * @covers ::__construct - */ - public function test_constructor() { - $this->assertInstanceOf( Wincher::class, $this->instance ); - $this->assertInstanceOf( - Wincher_Helper::class, - $this->getPropertyValue( $this->instance, 'wincher' ) - ); - } - - /** - * Tests if the expected conditionals are in place. - * - * @covers ::get_conditionals - */ - public function test_get_conditionals() { - $this->assertEquals( - [ - Admin_Conditional::class, - ], - Wincher::get_conditionals() - ); - } - - /** - * Tests the registration of the hooks. - * - * @covers ::register_hooks - */ - public function test_register_hooks() { - $this->instance->register_hooks(); - - $this->assertNotFalse( Monkey\Filters\has( 'wpseo_integration_toggles', [ $this->instance, 'add_integration_toggle' ] ) ); - } - - /** - * Tests the add_integration_toggle request function. - * - * @covers ::add_integration_toggle - */ - public function test_add_integration_toggle() { - $result = $this->instance->add_integration_toggle( $this->get_integration_toggles() ); - $this->assertEquals( - (object) [ - 'name' => 'Wincher integration', - 'setting' => 'wincher_integration_active', - 'label' => 'The Wincher integration offers the option to track specific keyphrases and gain insights in their positions.', - 'order' => 11, - 'disabled' => false, - ], - $result[2] - ); - } - - /** - * Tests the disabled add_integration_toggle request function. - * - * @covers ::add_integration_toggle - */ - public function test_add_integration_toggle_disabled() { - Monkey\Functions\stubs( [ 'is_multisite' => true ] ); - - $result = $this->instance->add_integration_toggle( $this->get_integration_toggles() ); - $this->assertEquals( - (object) [ - 'name' => 'Wincher integration', - 'setting' => 'wincher_integration_active', - 'label' => 'The Wincher integration offers the option to track specific keyphrases and gain insights in their positions.', - 'order' => 11, - 'disabled' => true, - ], - $result[2] - ); - } - - /** - * Tests the after_network_integration_toggle method. - * - * @covers ::after_network_integration_toggle - */ - public function test_after_network_integration_toggle() { - - $wincher_integration_toggle = (object) [ - 'setting' => 'wincher_integration_active', - 'disabled' => true, - ]; - - $this->instance->expects( 'get_disabled_note' )->once(); - - $this->instance->after_network_integration_toggle( $wincher_integration_toggle ); - } - - /** - * The integration toggles to test with. - * - * @return array The integration toggles. - */ - private function get_integration_toggles() { - return [ - (object) [ - 'name' => 'Semrush integration', - 'setting' => 'semrush_integration_active', - 'label' => 'The Semrush integration offers suggestions and insights for keywords related to the entered focus keyphrase.', - 'order' => 10, - ], - (object) [ - 'name' => 'Ryte integration', - 'setting' => 'ryte_indexability', - 'label' => 'Ryte will check weekly if your site is still indexable by search engines and Yoast SEO will notify you when this is not the case.', - 'read_more_label' => 'Read more about how Ryte works.', - 'read_more_url' => 'https://yoa.st/2an', - 'order' => 15, - ], - ]; - } -} diff --git a/tests/unit/routes/wincher-route-test.php b/tests/unit/routes/wincher-route-test.php index 30cc30cbcf8..d3320d57d43 100644 --- a/tests/unit/routes/wincher-route-test.php +++ b/tests/unit/routes/wincher-route-test.php @@ -168,6 +168,9 @@ public function test_register_routes() { 'permalink' => [ 'required' => false, ], + 'startAt' => [ + 'required' => false, + ], ], ] ); @@ -357,11 +360,17 @@ public function test_get_tracked_keyphrases() { ->with( 'keyphrases' ) ->andReturn( [ 'seo' ] ); + $request + ->expects( 'offsetGet' ) + ->with( 'startAt' ) + ->andReturn( '2023-01-01' ); + $this->keyphrases_action ->expects( 'get_tracked_keyphrases' ) ->with( [ 'seo' ], - 'https://example.com' + 'https://example.com', + '2023-01-01' ) ->andReturn( (object) [ 'status' => '200' ] ); @@ -387,11 +396,17 @@ public function test_get_tracked_keyphrases_without_permalink() { ->with( 'keyphrases' ) ->andReturn( [ 'seo' ] ); + $request + ->expects( 'offsetGet' ) + ->with( 'startAt' ) + ->andReturn( '2023-01-01' ); + $this->keyphrases_action ->expects( 'get_tracked_keyphrases' ) ->with( [ 'seo' ], - '' + '', + '2023-01-01' ) ->andReturn( (object) [ 'status' => '200' ] ); diff --git a/tests/unit/user-profiles-additions/user-interface/user-profiles-additions-ui-test.php b/tests/unit/user-profiles-additions/user-interface/user-profiles-additions-ui-test.php index 8f200078a33..41358e6c81b 100644 --- a/tests/unit/user-profiles-additions/user-interface/user-profiles-additions-ui-test.php +++ b/tests/unit/user-profiles-additions/user-interface/user-profiles-additions-ui-test.php @@ -4,6 +4,8 @@ use Brain\Monkey; use Mockery; +use WPSEO_Admin_Asset_Manager; +use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Conditionals\User_Profile_Conditional; use Yoast\WP\SEO\Tests\Unit\TestCase; use Yoast\WP\SEO\User_Profiles_Additions\User_Interface\User_Profiles_Additions_Ui; @@ -18,6 +20,20 @@ */ class User_Profiles_Additions_Ui_Test extends TestCase { + /** + * The mocked asset manager. + * + * @var Mockery\MockInterface|WPSEO_Admin_Asset_Manager + */ + protected $asset_manager; + + /** + * The mocked asset product helper. + * + * @var Mockery\MockInterface|Product_Helper + */ + protected $product_helper; + /** * The User_Profiles_Additions_Ui. * @@ -30,8 +46,26 @@ class User_Profiles_Additions_Ui_Test extends TestCase { */ protected function set_up() { parent::set_up(); + $this->asset_manager = Mockery::mock( 'WPSEO_Admin_Asset_Manager' ); + $this->product_helper = Mockery::mock( 'Yoast\WP\SEO\Helpers\Product_Helper' ); + + $this->instance = new User_Profiles_Additions_Ui( $this->asset_manager, $this->product_helper ); + } - $this->instance = new User_Profiles_Additions_Ui(); + /** + * Test construct method. + * + * @covers ::__construct + */ + public function test_construct() { + $this->assertInstanceOf( + WPSEO_Admin_Asset_Manager::class, + $this->getPropertyValue( $this->instance, 'asset_manager' ) + ); + $this->assertInstanceOf( + Product_Helper::class, + $this->getPropertyValue( $this->instance, 'product_helper' ) + ); } /** @@ -46,6 +80,26 @@ public function test_get_conditionals() { ); } + /** + * Test enqueue_assets method. + * + * @covers ::enqueue_assets + */ + public function test_enqueue_assets() { + + $this->product_helper + ->expects( 'is_premium' ) + ->once() + ->andReturn( true ); + + $this->asset_manager + ->expects( 'enqueue_style' ) + ->with( 'introductions' ) + ->once(); + + $this->instance->enqueue_assets(); + } + /** * Tests the registration of the hooks. * @@ -67,6 +121,11 @@ public function test_add_hook_to_user_profile() { $user = Mockery::mock( \WP_User::class ); + $this->product_helper + ->expects( 'is_premium' ) + ->once() + ->andReturn( false ); + Monkey\Actions\expectDone( 'wpseo_user_profile_additions' ) ->once() ->with( $user ); diff --git a/wp-seo-main.php b/wp-seo-main.php index a51197bafed..34bf056e95a 100644 --- a/wp-seo-main.php +++ b/wp-seo-main.php @@ -15,7 +15,7 @@ * {@internal Nobody should be able to overrule the real version number as this can cause * serious issues with the options, so no if ( ! defined() ).}} */ -define( 'WPSEO_VERSION', '21.5-RC1' ); +define( 'WPSEO_VERSION', '21.5-RC4' ); if ( ! defined( 'WPSEO_PATH' ) ) { @@ -35,7 +35,7 @@ define( 'YOAST_VENDOR_PREFIX_DIRECTORY', 'vendor_prefixed' ); define( 'YOAST_SEO_PHP_REQUIRED', '7.2.5' ); -define( 'YOAST_SEO_WP_TESTED', '6.3.2' ); +define( 'YOAST_SEO_WP_TESTED', '6.4' ); define( 'YOAST_SEO_WP_REQUIRED', '6.2' ); if ( ! defined( 'WPSEO_NAMESPACES' ) ) { diff --git a/wp-seo.php b/wp-seo.php index 2d7e1e6a731..e1cfb103a6d 100644 --- a/wp-seo.php +++ b/wp-seo.php @@ -8,7 +8,7 @@ * * @wordpress-plugin * Plugin Name: Yoast SEO - * Version: 21.5-RC1 + * Version: 21.5-RC4 * Plugin URI: https://yoa.st/1uj * Description: The first true all-in-one SEO solution for WordPress, including on-page content analysis, XML sitemaps and much more. * Author: Team Yoast diff --git a/yarn.lock b/yarn.lock index a727c5d0418..bc332f0ef83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -98,6 +98,21 @@ dependencies: "@babel/highlight" "^7.18.6" +"@babel/code-frame@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" + integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/code-frame@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + "@babel/compat-data@^7.13.11": version "7.13.15" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.15.tgz#7e8eea42d0b64fda2b375b22d06c605222e848f4" @@ -235,46 +250,25 @@ json5 "^2.2.1" semver "^6.3.0" -"@babel/core@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.9.tgz#6bae81a06d95f4d0dec5bb9d74bbc1f58babdcfe" - integrity sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.9" - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-module-transforms" "^7.17.7" - "@babel/helpers" "^7.17.9" - "@babel/parser" "^7.17.9" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.9" - "@babel/types" "^7.17.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" - -"@babel/core@^7.18.5": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.5.tgz#c597fa680e58d571c28dda9827669c78cdd7f000" - integrity sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ== +"@babel/core@^7.17.9", "@babel/core@^7.18.5": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.4.tgz#c6dc73242507b8e2a27fd13a9c1814f9fa34a659" + integrity sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA== dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.18.2" - "@babel/helper-compilation-targets" "^7.18.2" - "@babel/helper-module-transforms" "^7.18.0" - "@babel/helpers" "^7.18.2" - "@babel/parser" "^7.18.5" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.5" - "@babel/types" "^7.18.4" + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.21.4" + "@babel/generator" "^7.21.4" + "@babel/helper-compilation-targets" "^7.21.4" + "@babel/helper-module-transforms" "^7.21.2" + "@babel/helpers" "^7.21.0" + "@babel/parser" "^7.21.4" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.4" + "@babel/types" "^7.21.4" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.1" + json5 "^2.2.2" semver "^6.3.0" "@babel/core@^7.20.5": @@ -426,15 +420,6 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/generator@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" - integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== - dependencies: - "@babel/types" "^7.18.2" - "@jridgewell/gen-mapping" "^0.3.0" - jsesc "^2.5.1" - "@babel/generator@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95" @@ -475,6 +460,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b" @@ -493,7 +485,7 @@ browserslist "^4.14.5" semver "^6.3.0" -"@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.7": +"@babel/helper-compilation-targets@^7.16.7": version "7.17.7" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46" integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w== @@ -513,16 +505,6 @@ browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-compilation-targets@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" - integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== - dependencies: - "@babel/compat-data" "^7.17.10" - "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.20.2" - semver "^6.3.0" - "@babel/helper-compilation-targets@^7.20.0": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" @@ -544,6 +526,17 @@ lru-cache "^5.1.1" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz#770cd1ce0889097ceacb99418ee6934ef0572656" + integrity sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg== + dependencies: + "@babel/compat-data" "^7.21.4" + "@babel/helper-validator-option" "^7.21.0" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" + "@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.1", "@babel/helper-create-class-features-plugin@^7.17.6": version "7.17.6" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz#3778c1ed09a7f3e65e6d6e0f6fbfcc53809d92c9" @@ -570,6 +563,21 @@ "@babel/helper-replace-supers" "^7.19.1" "@babel/helper-split-export-declaration" "^7.18.6" +"@babel/helper-create-class-features-plugin@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz#97a61b385e57fe458496fad19f8e63b63c867de4" + integrity sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.12.13": version "7.12.17" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz#a2ac87e9e319269ac655b8d4415e94d38d663cb7" @@ -621,20 +629,15 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-environment-visitor@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" - integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== - "@babel/helper-environment-visitor@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== -"@babel/helper-environment-visitor@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz#ac3a56dbada59ed969d712cf527bd8271fe3eba8" - integrity sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA== +"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== "@babel/helper-explode-assignable-expression@^7.16.7": version "7.16.7" @@ -676,6 +679,14 @@ "@babel/template" "^7.20.7" "@babel/types" "^7.21.0" +"@babel/helper-function-name@^7.22.5": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + "@babel/helper-get-function-arity@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" @@ -718,6 +729,13 @@ dependencies: "@babel/types" "^7.18.9" +"@babel/helper-member-expression-to-functions@^7.22.15": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" + integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== + dependencies: + "@babel/types" "^7.23.0" + "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.13.12": version "7.13.12" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977" @@ -781,20 +799,6 @@ "@babel/traverse" "^7.13.0" "@babel/types" "^7.13.12" -"@babel/helper-module-transforms@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" - integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== - dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-simple-access" "^7.17.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/helper-validator-identifier" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.0" - "@babel/types" "^7.18.0" - "@babel/helper-module-transforms@^7.20.2": version "7.20.2" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712" @@ -844,6 +848,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-plugin-utils@7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" @@ -869,6 +880,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== +"@babel/helper-plugin-utils@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + "@babel/helper-remap-async-to-generator@^7.16.8": version "7.16.8" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" @@ -910,6 +926,15 @@ "@babel/traverse" "^7.19.1" "@babel/types" "^7.19.0" +"@babel/helper-replace-supers@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" + integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-simple-access@^7.13.12": version "7.13.12" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6" @@ -945,6 +970,13 @@ dependencies: "@babel/types" "^7.16.0" +"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-split-export-declaration@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05" @@ -966,15 +998,22 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-string-parser@^7.19.4": version "7.19.4" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== -"@babel/helper-string-parser@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" - integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== "@babel/helper-validator-identifier@^7.10.4", "@babel/helper-validator-identifier@^7.16.7": version "7.16.7" @@ -991,6 +1030,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/helper-validator-option@^7.12.17": version "7.12.17" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831" @@ -1048,15 +1092,6 @@ "@babel/traverse" "^7.17.9" "@babel/types" "^7.17.0" -"@babel/helpers@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" - integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== - dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.2" - "@babel/types" "^7.18.2" - "@babel/helpers@^7.20.5": version "7.20.6" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.6.tgz#e64778046b70e04779dfbdf924e7ebb45992c763" @@ -1102,6 +1137,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + "@babel/parser@^7.0.0": version "7.4.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872" @@ -1137,10 +1181,15 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8" integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA== -"@babel/parser@^7.18.5": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.5.tgz#337062363436a893a2d22faa60be5bb37091c83c" - integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw== +"@babel/parser@^7.20.7", "@babel/parser@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17" + integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== + +"@babel/parser@^7.22.15": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== "@babel/parser@^7.21.9", "@babel/parser@^7.22.0", "@babel/parser@^7.22.4": version "7.22.4" @@ -1544,6 +1593,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-class-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77" + integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-classes@^7.12.1", "@babel/plugin-transform-classes@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz#8f4b9562850cd973de3b498f1218796eb181ce00" @@ -2172,14 +2229,23 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/template@^7.20.7", "@babel/template@^7.21.9": - version "7.21.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.21.9.tgz#bf8dad2859130ae46088a99c1f265394877446fb" - integrity sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ== +"@babel/template@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/parser" "^7.21.9" - "@babel/types" "^7.21.5" + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" "@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.6", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.3", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": version "7.17.3" @@ -2245,22 +2311,6 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.18.5": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.5.tgz#94a8195ad9642801837988ab77f36e992d9a20cd" - integrity sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.18.2" - "@babel/helper-environment-visitor" "^7.18.2" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.18.5" - "@babel/types" "^7.18.4" - debug "^4.1.0" - globals "^11.1.0" - "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133" @@ -2277,6 +2327,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36" + integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q== + dependencies: + "@babel/code-frame" "^7.21.4" + "@babel/generator" "^7.21.4" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.21.4" + "@babel/types" "^7.21.4" + debug "^4.1.0" + globals "^11.1.0" + "@babel/traverse@^7.22.1": version "7.22.4" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.4.tgz#c3cf96c5c290bd13b55e29d025274057727664c0" @@ -2327,14 +2393,6 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4": - version "7.18.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" - integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - to-fast-properties "^2.0.0" - "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84" @@ -2353,6 +2411,24 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.4.tgz#2d5d6bb7908699b3b416409ffd3b5daa25b030d4" + integrity sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@base2/pretty-print-object@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz#371ba8be66d556812dc7fb169ebc3c08378f69d4" @@ -3142,6 +3218,11 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@kurkle/color@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f" + integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw== + "@lerna/add@6.4.1": version "6.4.1" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-6.4.1.tgz#fa20fe9ff875dc5758141262c8cde0d9a6481ec4" @@ -11015,6 +11096,18 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= +chart.js@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.2.1.tgz#d2bd5c98e9a0ae35408975b638f40513b067ba1d" + integrity sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw== + dependencies: + "@kurkle/color" "^0.3.0" + +chartjs-adapter-moment@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz#0f04c30d330b207c14bfb57dfaae9ce332f09102" + integrity sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA== + check-node-version@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/check-node-version/-/check-node-version-4.1.0.tgz#12ff45bfeb8dd591700a0ab848c21b2d8ceeeb94" @@ -26554,6 +26647,11 @@ react-base16-styling@^0.5.1: lodash.flow "^3.3.0" pure-color "^1.2.0" +react-chartjs-2@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz#43c1e3549071c00a1a083ecbd26c1ad34d385f5d" + integrity sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA== + react-colorful@4.4.4: version "4.4.4" resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-4.4.4.tgz#38e7c5b7075bbf63d3cce22d8c61a439a58b7561" @@ -28404,6 +28502,11 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + semver@^7.0.0, semver@^7.3.7: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"